pumpfun/
lib.rs

1#![doc = include_str!("../RUSTDOC.md")]
2
3pub mod accounts;
4pub mod common;
5pub mod constants;
6pub mod error;
7pub mod instructions;
8pub mod utils;
9
10use common::types::{Cluster, PriorityFee};
11use solana_client::nonblocking::rpc_client::RpcClient;
12use solana_sdk::{
13    compute_budget::ComputeBudgetInstruction,
14    instruction::Instruction,
15    pubkey::Pubkey,
16    signature::{Keypair, Signature},
17    signer::Signer,
18};
19use spl_associated_token_account::get_associated_token_address;
20#[cfg(feature = "create-ata")]
21use spl_associated_token_account::instruction::create_associated_token_account;
22#[cfg(feature = "close-ata")]
23use spl_token::instruction::close_account;
24use std::sync::Arc;
25use utils::transaction::get_transaction;
26
27/// Main client for interacting with the Pump.fun program
28///
29/// This struct provides the primary interface for interacting with the Pump.fun
30/// token platform on Solana. It handles connection to the Solana network and provides
31/// methods for token creation, buying, and selling using bonding curves.
32///
33/// # Examples
34///
35/// ```no_run
36/// use pumpfun::{PumpFun, common::types::{Cluster, PriorityFee}};
37/// use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair};
38/// use std::sync::Arc;
39///
40/// // Create a new client connected to devnet
41/// let payer = Arc::new(Keypair::new());
42/// let commitment = CommitmentConfig::confirmed();
43/// let priority_fee = PriorityFee::default();
44/// let cluster = Cluster::devnet(commitment, priority_fee);
45/// let client = PumpFun::new(payer, cluster);
46/// ```
47pub struct PumpFun {
48    /// Keypair used to sign transactions
49    pub payer: Arc<Keypair>,
50    /// RPC client for Solana network requests
51    pub rpc: Arc<RpcClient>,
52    /// Cluster configuration
53    pub cluster: Cluster,
54}
55
56impl PumpFun {
57    /// Creates a new PumpFun client instance
58    ///
59    /// Initializes a new client for interacting with the Pump.fun program on Solana.
60    /// This client manages connection to the Solana network and provides methods for
61    /// creating, buying, and selling tokens.
62    ///
63    /// # Arguments
64    ///
65    /// * `payer` - Keypair used to sign and pay for transactions
66    /// * `cluster` - Solana cluster configuration including RPC endpoints and transaction parameters
67    ///
68    /// # Returns
69    ///
70    /// Returns a new PumpFun client instance configured with the provided parameters
71    ///
72    /// # Examples
73    ///
74    /// ```no_run
75    /// use pumpfun::{PumpFun, common::types::{Cluster, PriorityFee}};
76    /// use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair};
77    /// use std::sync::Arc;
78    ///
79    /// let payer = Arc::new(Keypair::new());
80    /// let commitment = CommitmentConfig::confirmed();
81    /// let priority_fee = PriorityFee::default();
82    /// let cluster = Cluster::devnet(commitment, priority_fee);
83    /// let client = PumpFun::new(payer, cluster);
84    /// ```
85    pub fn new(payer: Arc<Keypair>, cluster: Cluster) -> Self {
86        // Create Solana RPC Client with HTTP endpoint
87        let rpc = Arc::new(RpcClient::new_with_commitment(
88            cluster.rpc.http.clone(),
89            cluster.commitment,
90        ));
91
92        // Return configured PumpFun client
93        Self {
94            payer,
95            rpc,
96            cluster,
97        }
98    }
99
100    /// Creates a new token with metadata by uploading metadata to IPFS and initializing on-chain accounts
101    ///
102    /// This method handles the complete process of creating a new token on Pump.fun:
103    /// 1. Uploads token metadata and image to IPFS
104    /// 2. Creates a new SPL token with the provided mint keypair
105    /// 3. Initializes the bonding curve that determines token pricing
106    /// 4. Sets up metadata using the Metaplex standard
107    ///
108    /// # Arguments
109    ///
110    /// * `mint` - Keypair for the new token mint account that will be created
111    /// * `metadata` - Token metadata including name, symbol, description and image file
112    /// * `priority_fee` - Optional priority fee configuration for compute units. If None, uses the
113    ///   default from the cluster configuration
114    ///
115    /// # Returns
116    ///
117    /// Returns the transaction signature if successful, or a ClientError if the operation fails
118    ///
119    /// # Errors
120    ///
121    /// Returns an error if:
122    /// - Metadata upload to IPFS fails
123    /// - Transaction creation fails
124    /// - Transaction execution on Solana fails
125    ///
126    /// # Examples
127    ///
128    /// ```no_run
129    /// # use pumpfun::{PumpFun, common::types::{Cluster, PriorityFee}, utils::CreateTokenMetadata};
130    /// # use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair};
131    /// # use std::sync::Arc;
132    /// #
133    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
134    /// # let payer = Arc::new(Keypair::new());
135    /// # let commitment = CommitmentConfig::confirmed();
136    /// # let cluster = Cluster::devnet(commitment, PriorityFee::default());
137    /// # let client = PumpFun::new(payer, cluster);
138    /// let mint = Keypair::new();
139    /// let metadata = CreateTokenMetadata {
140    ///     name: "My Token".to_string(),
141    ///     symbol: "MYTKN".to_string(),
142    ///     description: "A test token created with Pump.fun".to_string(),
143    ///     file: "path/to/image.png".to_string(),
144    ///     twitter: None,
145    ///     telegram: None,
146    ///     website: Some("https://example.com".to_string()),
147    /// };
148    ///
149    /// let signature = client.create(mint, metadata, None).await?;
150    /// println!("Token created! Signature: {}", signature);
151    /// # Ok(())
152    /// # }
153    /// ```
154    pub async fn create(
155        &self,
156        mint: Keypair,
157        metadata: utils::CreateTokenMetadata,
158        priority_fee: Option<PriorityFee>,
159    ) -> Result<Signature, error::ClientError> {
160        // First upload metadata and image to IPFS
161        let ipfs: utils::TokenMetadataResponse = utils::create_token_metadata(metadata)
162            .await
163            .map_err(error::ClientError::UploadMetadataError)?;
164
165        // Add priority fee if provided or default to cluster priority fee
166        let priority_fee = priority_fee.unwrap_or(self.cluster.priority_fee);
167        let mut instructions = Self::get_priority_fee_instructions(&priority_fee);
168
169        // Add create token instruction
170        let create_ix = self.get_create_instruction(&mint, ipfs);
171        instructions.push(create_ix);
172
173        // Create and sign transaction
174        let transaction = get_transaction(
175            self.rpc.clone(),
176            self.payer.clone(),
177            &instructions,
178            Some(&[&mint]),
179            #[cfg(feature = "versioned-tx")]
180            None,
181        )
182        .await?;
183
184        // Send and confirm transaction
185        let signature = self
186            .rpc
187            .send_and_confirm_transaction(&transaction)
188            .await
189            .map_err(error::ClientError::SolanaClientError)?;
190
191        Ok(signature)
192    }
193
194    /// Creates a new token and immediately buys an initial amount in a single atomic transaction
195    ///
196    /// This method combines token creation and an initial purchase into a single atomic transaction.
197    /// This is often preferred for new token launches as it:
198    /// 1. Creates the token and its bonding curve
199    /// 2. Makes an initial purchase to establish liquidity
200    /// 3. Guarantees that the creator becomes the first holder
201    ///
202    /// The entire operation is executed as a single transaction, ensuring atomicity.
203    ///
204    /// # Arguments
205    ///
206    /// * `mint` - Keypair for the new token mint account that will be created
207    /// * `metadata` - Token metadata including name, symbol, description and image file
208    /// * `amount_sol` - Amount of SOL to spend on the initial buy, in lamports (1 SOL = 1,000,000,000 lamports)
209    /// * `slippage_basis_points` - Optional maximum acceptable slippage in basis points (1 bp = 0.01%).
210    ///   If None, defaults to 500 (5%)
211    /// * `priority_fee` - Optional priority fee configuration for compute units. If None, uses the
212    ///   default from the cluster configuration
213    ///
214    /// # Returns
215    ///
216    /// Returns the transaction signature if successful, or a ClientError if the operation fails
217    ///
218    /// # Errors
219    ///
220    /// Returns an error if:
221    /// - Metadata upload to IPFS fails
222    /// - Account retrieval fails
223    /// - Transaction creation fails
224    /// - Transaction execution on Solana fails
225    ///
226    /// # Examples
227    ///
228    /// ```no_run
229    /// # use pumpfun::{PumpFun, common::types::{Cluster, PriorityFee}, utils::CreateTokenMetadata};
230    /// # use solana_sdk::{commitment_config::CommitmentConfig, native_token::sol_to_lamports, signature::Keypair};
231    /// # use std::sync::Arc;
232    /// #
233    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
234    /// # let payer = Arc::new(Keypair::new());
235    /// # let commitment = CommitmentConfig::confirmed();
236    /// # let cluster = Cluster::devnet(commitment, PriorityFee::default());
237    /// # let client = PumpFun::new(payer, cluster);
238    /// let mint = Keypair::new();
239    /// let metadata = CreateTokenMetadata {
240    ///     name: "My Token".to_string(),
241    ///     symbol: "MYTKN".to_string(),
242    ///     description: "A test token created with Pump.fun".to_string(),
243    ///     file: "path/to/image.png".to_string(),
244    ///     twitter: None,
245    ///     telegram: None,
246    ///     website: Some("https://example.com".to_string()),
247    /// };
248    ///
249    /// // Create token and buy 0.1 SOL worth with 5% slippage tolerance
250    /// let amount_sol = sol_to_lamports(0.1f64); // 0.1 SOL in lamports
251    /// let slippage_bps = Some(500); // 5%
252    /// let track_volume = Some(true); // Track this initial buy in volume stats
253    ///
254    /// let signature = client.create_and_buy(mint, metadata, amount_sol, track_volume, slippage_bps, None).await?;
255    /// println!("Token created and bought! Signature: {}", signature);
256    /// # Ok(())
257    /// # }
258    /// ```
259    pub async fn create_and_buy(
260        &self,
261        mint: Keypair,
262        metadata: utils::CreateTokenMetadata,
263        amount_sol: u64,
264        track_volume: Option<bool>,
265        slippage_basis_points: Option<u64>,
266        priority_fee: Option<PriorityFee>,
267    ) -> Result<Signature, error::ClientError> {
268        // Upload metadata to IPFS first
269        let ipfs: utils::TokenMetadataResponse = utils::create_token_metadata(metadata)
270            .await
271            .map_err(error::ClientError::UploadMetadataError)?;
272
273        // Add priority fee if provided or default to cluster priority fee
274        let priority_fee = priority_fee.unwrap_or(self.cluster.priority_fee);
275        let mut instructions = Self::get_priority_fee_instructions(&priority_fee);
276
277        // Add create token instruction
278        let create_ix = self.get_create_instruction(&mint, ipfs);
279        instructions.push(create_ix);
280
281        // Add buy instruction
282        let buy_ix = self
283            .get_buy_instructions(
284                mint.pubkey(),
285                amount_sol,
286                track_volume,
287                slippage_basis_points,
288            )
289            .await?;
290        instructions.extend(buy_ix);
291
292        // Create and sign transaction
293        let transaction = get_transaction(
294            self.rpc.clone(),
295            self.payer.clone(),
296            &instructions,
297            Some(&[&mint]),
298            #[cfg(feature = "versioned-tx")]
299            None,
300        )
301        .await?;
302
303        // Send and confirm transaction
304        let signature = self
305            .rpc
306            .send_and_confirm_transaction(&transaction)
307            .await
308            .map_err(error::ClientError::SolanaClientError)?;
309
310        Ok(signature)
311    }
312
313    /// Buys tokens from a bonding curve by spending SOL
314    ///
315    /// This method purchases tokens from a bonding curve by providing SOL. The amount of tokens
316    /// received is determined by the bonding curve formula for the specific token. As more tokens
317    /// are purchased, the price increases according to the curve function.
318    ///
319    /// The method:
320    /// 1. Calculates how many tokens will be received for the given SOL amount
321    /// 2. Creates an associated token account for the buyer if needed
322    /// 3. Executes the buy transaction with slippage protection
323    ///
324    /// A portion of the SOL is taken as a fee according to the global configuration.
325    ///
326    /// # Arguments
327    ///
328    /// * `mint` - Public key of the token mint to buy
329    /// * `amount_sol` - Amount of SOL to spend, in lamports (1 SOL = 1,000,000,000 lamports)
330    /// * `slippage_basis_points` - Optional maximum acceptable slippage in basis points (1 bp = 0.01%).
331    ///   If None, defaults to 500 (5%)
332    /// * `priority_fee` - Optional priority fee configuration for compute units. If None, uses the
333    ///   default from the cluster configuration
334    ///
335    /// # Returns
336    ///
337    /// Returns the transaction signature if successful, or a ClientError if the operation fails
338    ///
339    /// # Errors
340    ///
341    /// Returns an error if:
342    /// - The bonding curve account cannot be found
343    /// - The buy price calculation fails
344    /// - Transaction creation fails
345    /// - Transaction execution on Solana fails
346    ///
347    /// # Examples
348    ///
349    /// ```no_run
350    /// # use pumpfun::{PumpFun, common::types::{Cluster, PriorityFee}};
351    /// # use solana_sdk::{commitment_config::CommitmentConfig, native_token::sol_to_lamports, pubkey, signature::Keypair};
352    /// # use std::sync::Arc;
353    /// #
354    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
355    /// # let payer = Arc::new(Keypair::new());
356    /// # let commitment = CommitmentConfig::confirmed();
357    /// # let cluster = Cluster::devnet(commitment, PriorityFee::default());
358    /// # let client = PumpFun::new(payer, cluster);
359    /// let token_mint = pubkey!("SoMeTokenM1ntAddr3ssXXXXXXXXXXXXXXXXXXXXXXX");
360    ///
361    /// // Buy 0.01 SOL worth of tokens with 3% max slippage
362    /// let amount_sol = sol_to_lamports(0.01f64); // 0.01 SOL in lamports
363    /// let slippage_bps = Some(300); // 3%
364    /// let track_volume = Some(true); // Track this buy in volume stats
365    ///
366    /// let signature = client.buy(token_mint, amount_sol, track_volume, slippage_bps, None).await?;
367    /// println!("Tokens purchased! Signature: {}", signature);
368    /// # Ok(())
369    /// # }
370    /// ```
371    pub async fn buy(
372        &self,
373        mint: Pubkey,
374        amount_sol: u64,
375        track_volume: Option<bool>,
376        slippage_basis_points: Option<u64>,
377        priority_fee: Option<PriorityFee>,
378    ) -> Result<Signature, error::ClientError> {
379        // Add priority fee if provided or default to cluster priority fee
380        let priority_fee = priority_fee.unwrap_or(self.cluster.priority_fee);
381        let mut instructions = Self::get_priority_fee_instructions(&priority_fee);
382
383        // Add buy instruction
384        let buy_ix = self
385            .get_buy_instructions(mint, amount_sol, track_volume, slippage_basis_points)
386            .await?;
387        instructions.extend(buy_ix);
388
389        // Create and sign transaction
390        let transaction = get_transaction(
391            self.rpc.clone(),
392            self.payer.clone(),
393            &instructions,
394            None,
395            #[cfg(feature = "versioned-tx")]
396            None,
397        )
398        .await?;
399
400        // Send and confirm transaction
401        let signature = self
402            .rpc
403            .send_and_confirm_transaction(&transaction)
404            .await
405            .map_err(error::ClientError::SolanaClientError)?;
406
407        Ok(signature)
408    }
409
410    /// Sells tokens back to the bonding curve in exchange for SOL
411    ///
412    /// This method sells tokens back to the bonding curve, receiving SOL in return. The amount of SOL
413    /// received is determined by the bonding curve formula for the specific token. As more tokens
414    /// are sold, the price decreases according to the curve function.
415    ///
416    /// The method:
417    /// 1. Determines how many tokens to sell (all tokens or a specific amount)
418    /// 2. Calculates how much SOL will be received for the tokens
419    /// 3. Executes the sell transaction with slippage protection
420    ///
421    /// A portion of the SOL is taken as a fee according to the global configuration.
422    ///
423    /// # Arguments
424    ///
425    /// * `mint` - Public key of the token mint to sell
426    /// * `amount_token` - Optional amount of tokens to sell in base units. If None, sells the entire balance
427    /// * `slippage_basis_points` - Optional maximum acceptable slippage in basis points (1 bp = 0.01%).
428    ///   If None, defaults to 500 (5%)
429    /// * `priority_fee` - Optional priority fee configuration for compute units. If None, uses the
430    ///   default from the cluster configuration
431    ///
432    /// # Returns
433    ///
434    /// Returns the transaction signature if successful, or a ClientError if the operation fails
435    ///
436    /// # Errors
437    ///
438    /// Returns an error if:
439    /// - The token account cannot be found
440    /// - The bonding curve account cannot be found
441    /// - The sell price calculation fails
442    /// - Transaction creation fails
443    /// - Transaction execution on Solana fails
444    ///
445    /// # Examples
446    ///
447    /// ```no_run
448    /// # use pumpfun::{PumpFun, common::types::{Cluster, PriorityFee}};
449    /// # use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair, pubkey};
450    /// # use std::sync::Arc;
451    /// #
452    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
453    /// # let payer = Arc::new(Keypair::new());
454    /// # let commitment = CommitmentConfig::confirmed();
455    /// # let cluster = Cluster::devnet(commitment, PriorityFee::default());
456    /// # let client = PumpFun::new(payer, cluster);
457    /// let token_mint = pubkey!("SoMeTokenM1ntAddr3ssXXXXXXXXXXXXXXXXXXXXXXX");
458    ///
459    /// // Sell 1000 tokens with 2% max slippage
460    /// let amount_tokens = Some(1000);
461    /// let slippage_bps = Some(200); // 2%
462    ///
463    /// let signature = client.sell(token_mint, amount_tokens, slippage_bps, None).await?;
464    /// println!("Tokens sold! Signature: {}", signature);
465    ///
466    /// // Or sell all tokens with default slippage (5%)
467    /// let signature = client.sell(token_mint, None, None, None).await?;
468    /// println!("All tokens sold! Signature: {}", signature);
469    /// # Ok(())
470    /// # }
471    /// ```
472    pub async fn sell(
473        &self,
474        mint: Pubkey,
475        amount_token: Option<u64>,
476        slippage_basis_points: Option<u64>,
477        priority_fee: Option<PriorityFee>,
478    ) -> Result<Signature, error::ClientError> {
479        // Add priority fee if provided or default to cluster priority fee
480        let priority_fee = priority_fee.unwrap_or(self.cluster.priority_fee);
481        let mut instructions = Self::get_priority_fee_instructions(&priority_fee);
482
483        // Add sell instruction
484        let sell_ix = self
485            .get_sell_instructions(mint, amount_token, slippage_basis_points)
486            .await?;
487        instructions.extend(sell_ix);
488
489        // Create and sign transaction
490        let transaction = get_transaction(
491            self.rpc.clone(),
492            self.payer.clone(),
493            &instructions,
494            None,
495            #[cfg(feature = "versioned-tx")]
496            None,
497        )
498        .await?;
499
500        // Send and confirm transaction
501        let signature = self
502            .rpc
503            .send_and_confirm_transaction(&transaction)
504            .await
505            .map_err(error::ClientError::SolanaClientError)?;
506
507        Ok(signature)
508    }
509
510    /// Subscribes to real-time events from the Pump.fun program
511    ///
512    /// This method establishes a WebSocket connection to the Solana cluster and subscribes
513    /// to program log events from the Pump.fun program. It parses the emitted events into
514    /// structured data types and delivers them through the provided callback function.
515    ///
516    /// Event types include:
517    /// - `CreateEvent`: Emitted when a new token is created
518    /// - `TradeEvent`: Emitted when tokens are bought or sold
519    /// - `CompleteEvent`: Emitted when a bonding curve operation completes
520    /// - `SetParamsEvent`: Emitted when global parameters are updated
521    ///
522    /// # Arguments
523    ///
524    /// * `mentioned` - Optional public key to filter events by mentions. If None, subscribes to all Pump.fun events
525    /// * `commitment` - Optional commitment level for the subscription. If None, uses the
526    ///   default from the cluster configuration
527    /// * `callback` - A function that will be called for each event with the following parameters:
528    ///   * `signature`: The transaction signature as a String
529    ///   * `event`: The parsed PumpFunEvent if successful, or None if parsing failed
530    ///   * `error`: Any error that occurred during parsing, or None if successful
531    ///   * `response`: The complete RPC logs response for additional context
532    ///
533    /// # Returns
534    ///
535    /// Returns a `Subscription` object that manages the lifecycle of the subscription.
536    /// When this object is dropped, the subscription is automatically terminated. If
537    /// the subscription cannot be established, returns a ClientError.
538    ///
539    /// # Errors
540    ///
541    /// Returns an error if:
542    /// - The WebSocket connection cannot be established
543    /// - The subscription request fails
544    ///
545    /// # Examples
546    ///
547    /// ```no_run
548    /// # use pumpfun::{PumpFun, common::types::{Cluster, PriorityFee}};
549    /// # use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair};
550    /// # use std::{sync::Arc, error::Error};
551    /// #
552    /// # async fn example() -> Result<(), Box<dyn Error>> {
553    /// # let payer = Arc::new(Keypair::new());
554    /// # let commitment = CommitmentConfig::confirmed();
555    /// # let cluster = Cluster::devnet(commitment, PriorityFee::default());
556    /// # let client = PumpFun::new(payer, cluster);
557    /// #
558    /// // Subscribe to token events
559    /// let subscription = client.subscribe(None, None, |signature, event, error, _| {
560    ///     match event {
561    ///         Some(pumpfun::common::stream::PumpFunEvent::Create(create_event)) => {
562    ///             println!("New token created: {} ({})", create_event.name, create_event.symbol);
563    ///             println!("Mint address: {}", create_event.mint);
564    ///         },
565    ///         Some(pumpfun::common::stream::PumpFunEvent::Trade(trade_event)) => {
566    ///             let action = if trade_event.is_buy { "bought" } else { "sold" };
567    ///             println!(
568    ///                 "User {} {} {} tokens for {} SOL",
569    ///                 trade_event.user,
570    ///                 action,
571    ///                 trade_event.token_amount,
572    ///                 trade_event.sol_amount as f64 / 1_000_000_000.0
573    ///             );
574    ///         },
575    ///         Some(event) => println!("Other event received: {:#?}", event),
576    ///         None => {
577    ///             if let Some(err) = error {
578    ///                 eprintln!("Error parsing event in tx {}: {}", signature, err);
579    ///             }
580    ///         }
581    ///     }
582    /// }).await?;
583    ///
584    /// // Keep the subscription active
585    /// // When no longer needed, drop the subscription to unsubscribe
586    /// # Ok(())
587    /// # }
588    /// ```
589    #[cfg(feature = "stream")]
590    pub async fn subscribe<F>(
591        &self,
592        mentioned: Option<String>,
593        commitment: Option<solana_sdk::commitment_config::CommitmentConfig>,
594        callback: F,
595    ) -> Result<common::stream::Subscription, error::ClientError>
596    where
597        F: Fn(
598                String,
599                Option<common::stream::PumpFunEvent>,
600                Option<Box<dyn std::error::Error + Send + Sync>>,
601                solana_client::rpc_response::Response<solana_client::rpc_response::RpcLogsResponse>,
602            ) + Send
603            + Sync
604            + 'static,
605    {
606        common::stream::subscribe(self.cluster.clone(), mentioned, commitment, callback).await
607    }
608
609    /// Creates compute budget instructions for priority fees
610    ///
611    /// Generates Solana compute budget instructions based on the provided priority fee
612    /// configuration. These instructions are used to set the maximum compute units a
613    /// transaction can consume and the price per compute unit, which helps prioritize
614    /// transaction processing during network congestion.
615    ///
616    /// # Arguments
617    ///
618    /// * `priority_fee` - Priority fee configuration containing optional unit limit and unit price
619    ///
620    /// # Returns
621    ///
622    /// Returns a vector of instructions to set compute budget parameters, which can be
623    /// empty if no priority fee parameters are provided
624    ///
625    /// # Examples
626    ///
627    /// ```no_run
628    /// # use pumpfun::{PumpFun, common::types::PriorityFee};
629    /// # use solana_sdk::instruction::Instruction;
630    /// #
631    /// // Set both compute unit limit and price
632    /// let priority_fee = PriorityFee {
633    ///     unit_limit: Some(200_000),
634    ///     unit_price: Some(1_000), // 1000 micro-lamports per compute unit
635    /// };
636    ///
637    /// let compute_instructions: Vec<Instruction> = PumpFun::get_priority_fee_instructions(&priority_fee);
638    /// ```
639    pub fn get_priority_fee_instructions(priority_fee: &PriorityFee) -> Vec<Instruction> {
640        let mut instructions = Vec::new();
641
642        if let Some(limit) = priority_fee.unit_limit {
643            let limit_ix = ComputeBudgetInstruction::set_compute_unit_limit(limit);
644            instructions.push(limit_ix);
645        }
646
647        if let Some(price) = priority_fee.unit_price {
648            let price_ix = ComputeBudgetInstruction::set_compute_unit_price(price);
649            instructions.push(price_ix);
650        }
651
652        instructions
653    }
654
655    /// Creates an instruction for initializing a new token
656    ///
657    /// Generates a Solana instruction to create a new token with a bonding curve on Pump.fun.
658    /// This instruction will initialize the token mint, metadata, and bonding curve accounts.
659    ///
660    /// # Arguments
661    ///
662    /// * `mint` - Keypair for the new token mint account that will be created
663    /// * `ipfs` - Token metadata response from IPFS upload containing name, symbol, and URI
664    ///
665    /// # Returns
666    ///
667    /// Returns a Solana instruction for creating a new token
668    ///
669    /// # Examples
670    ///
671    /// ```no_run
672    /// # use pumpfun::{PumpFun, common::types::{Cluster, PriorityFee}, utils};
673    /// # use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair};
674    /// # use std::sync::Arc;
675    /// #
676    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
677    /// # let payer = Arc::new(Keypair::new());
678    /// # let commitment = CommitmentConfig::confirmed();
679    /// # let cluster = Cluster::devnet(commitment, PriorityFee::default());
680    /// # let client = PumpFun::new(payer, cluster);
681    /// #
682    /// let mint = Keypair::new();
683    /// let metadata_response = utils::create_token_metadata(
684    ///     utils::CreateTokenMetadata {
685    ///         name: "Example Token".to_string(),
686    ///         symbol: "EXTKN".to_string(),
687    ///         description: "An example token".to_string(),
688    ///         file: "path/to/image.png".to_string(),
689    ///         twitter: None,
690    ///         telegram: None,
691    ///         website: None,
692    ///     }
693    /// ).await?;
694    ///
695    /// let create_instruction = client.get_create_instruction(&mint, metadata_response);
696    /// # Ok(())
697    /// # }
698    /// ```
699    pub fn get_create_instruction(
700        &self,
701        mint: &Keypair,
702        ipfs: utils::TokenMetadataResponse,
703    ) -> Instruction {
704        instructions::create(
705            &self.payer,
706            mint,
707            instructions::Create {
708                name: ipfs.metadata.name,
709                symbol: ipfs.metadata.symbol,
710                uri: ipfs.metadata.image,
711                creator: self.payer.pubkey(),
712            },
713        )
714    }
715
716    /// Generates instructions for buying tokens from a bonding curve
717    ///
718    /// Creates a set of Solana instructions needed to purchase tokens using SOL. These
719    /// instructions may include creating an associated token account if needed, and the actual
720    /// buy instruction with slippage protection.
721    ///
722    /// # Arguments
723    ///
724    /// * `mint` - Public key of the token mint to buy
725    /// * `amount_sol` - Amount of SOL to spend, in lamports (1 SOL = 1,000,000,000 lamports)
726    /// * `slippage_basis_points` - Optional maximum acceptable slippage in basis points (1 bp = 0.01%).
727    ///   If None, defaults to 500 (5%)
728    ///
729    /// # Returns
730    ///
731    /// Returns a vector of Solana instructions if successful, or a ClientError if the operation fails
732    ///
733    /// # Errors
734    ///
735    /// Returns an error if:
736    /// - The global account or bonding curve account cannot be fetched
737    /// - The buy price calculation fails
738    /// - Token account-related operations fail
739    ///
740    /// # Examples
741    ///
742    /// ```no_run
743    /// # use pumpfun::{PumpFun, common::types::{Cluster, PriorityFee}};
744    /// # use solana_sdk::{commitment_config::CommitmentConfig, native_token::sol_to_lamports, signature::Keypair, pubkey};
745    /// # use std::sync::Arc;
746    /// #
747    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
748    /// # let payer = Arc::new(Keypair::new());
749    /// # let commitment = CommitmentConfig::confirmed();
750    /// # let cluster = Cluster::devnet(commitment, PriorityFee::default());
751    /// # let client = PumpFun::new(payer, cluster);
752    /// #
753    /// let mint = pubkey!("TokenM1ntPubk3yXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
754    /// let amount_sol = sol_to_lamports(0.01); // 0.01 SOL
755    /// let slippage_bps = Some(300); // 3%
756    /// let track_volume = Some(true); // Track this buy in volume stats
757    ///
758    /// let buy_instructions = client.get_buy_instructions(mint, amount_sol, track_volume, slippage_bps).await?;
759    /// # Ok(())
760    /// # }
761    /// ```
762    pub async fn get_buy_instructions(
763        &self,
764        mint: Pubkey,
765        amount_sol: u64,
766        track_volume: Option<bool>,
767        slippage_basis_points: Option<u64>,
768    ) -> Result<Vec<Instruction>, error::ClientError> {
769        // Get accounts and calculate buy amounts
770        let global_account = self.get_global_account().await?;
771        let mut bonding_curve_account: Option<accounts::BondingCurveAccount> = None;
772        let buy_amount = {
773            let bonding_curve_pda = Self::get_bonding_curve_pda(&mint)
774                .ok_or(error::ClientError::BondingCurveNotFound)?;
775            if self.rpc.get_account(&bonding_curve_pda).await.is_err() {
776                global_account.get_initial_buy_price(amount_sol)
777            } else {
778                bonding_curve_account = self.get_bonding_curve_account(&mint).await.ok();
779                bonding_curve_account
780                    .as_ref()
781                    .unwrap()
782                    .get_buy_price(amount_sol)
783                    .map_err(error::ClientError::BondingCurveError)?
784            }
785        };
786        let buy_amount_with_slippage =
787            utils::calculate_with_slippage_buy(amount_sol, slippage_basis_points.unwrap_or(500));
788
789        let mut instructions = Vec::new();
790
791        // Create Associated Token Account if needed
792        #[cfg(feature = "create-ata")]
793        {
794            let ata: Pubkey = get_associated_token_address(&self.payer.pubkey(), &mint);
795            if self.rpc.get_account(&ata).await.is_err() {
796                instructions.push(create_associated_token_account(
797                    &self.payer.pubkey(),
798                    &self.payer.pubkey(),
799                    &mint,
800                    &constants::accounts::TOKEN_PROGRAM,
801                ));
802            }
803        }
804
805        // Add buy instruction
806        instructions.push(instructions::buy(
807            &self.payer,
808            &mint,
809            &global_account.fee_recipient,
810            &bonding_curve_account.map_or(self.payer.pubkey(), |bc| bc.creator),
811            instructions::Buy {
812                amount: buy_amount,
813                max_sol_cost: buy_amount_with_slippage,
814                track_volume,
815            },
816        ));
817
818        Ok(instructions)
819    }
820
821    /// Generates instructions for selling tokens back to a bonding curve
822    ///
823    /// Creates a set of Solana instructions needed to sell tokens in exchange for SOL. These
824    /// instructions include the sell instruction with slippage protection and may include
825    /// closing the associated token account if all tokens are being sold and the feature
826    /// is enabled.
827    ///
828    /// # Arguments
829    ///
830    /// * `mint` - Public key of the token mint to sell
831    /// * `amount_token` - Optional amount of tokens to sell in base units. If None, sells the entire balance
832    /// * `slippage_basis_points` - Optional maximum acceptable slippage in basis points (1 bp = 0.01%).
833    ///   If None, defaults to 500 (5%)
834    ///
835    /// # Returns
836    ///
837    /// Returns a vector of Solana instructions if successful, or a ClientError if the operation fails
838    ///
839    /// # Errors
840    ///
841    /// Returns an error if:
842    /// - The token account or token balance cannot be fetched
843    /// - The global account or bonding curve account cannot be fetched
844    /// - The sell price calculation fails
845    /// - Token account closing operations fail (when applicable)
846    ///
847    /// # Examples
848    ///
849    /// ```no_run
850    /// # use pumpfun::{PumpFun, common::types::{Cluster, PriorityFee}};
851    /// # use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair, pubkey};
852    /// # use std::sync::Arc;
853    /// #
854    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
855    /// # let payer = Arc::new(Keypair::new());
856    /// # let commitment = CommitmentConfig::confirmed();
857    /// # let cluster = Cluster::devnet(commitment, PriorityFee::default());
858    /// # let client = PumpFun::new(payer, cluster);
859    /// #
860    /// let mint = pubkey!("TokenM1ntPubk3yXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
861    /// let amount_tokens = Some(1000); // Sell 1000 tokens
862    /// let slippage_bps = Some(200); // 2%
863    ///
864    /// let sell_instructions = client.get_sell_instructions(mint, amount_tokens, slippage_bps).await?;
865    ///
866    /// // Or to sell all tokens:
867    /// let sell_all_instructions = client.get_sell_instructions(mint, None, None).await?;
868    /// # Ok(())
869    /// # }
870    /// ```
871    pub async fn get_sell_instructions(
872        &self,
873        mint: Pubkey,
874        amount_token: Option<u64>,
875        slippage_basis_points: Option<u64>,
876    ) -> Result<Vec<Instruction>, error::ClientError> {
877        // Get ATA
878        let ata: Pubkey = get_associated_token_address(&self.payer.pubkey(), &mint);
879
880        // Get token balance
881        let token_balance = if amount_token.is_none() || cfg!(feature = "close-ata") {
882            // We need the balance if amount_token is None OR if the close-ata feature is enabled
883            let balance = self.rpc.get_token_account_balance(&ata).await?;
884            Some(balance.amount.parse::<u64>().unwrap())
885        } else {
886            None
887        };
888
889        // Determine amount to sell
890        let amount = amount_token.unwrap_or_else(|| token_balance.unwrap());
891
892        // Calculate min sol output
893        let global_account = self.get_global_account().await?;
894        let bonding_curve_account = self.get_bonding_curve_account(&mint).await?;
895        let min_sol_output = bonding_curve_account
896            .get_sell_price(amount, global_account.fee_basis_points)
897            .map_err(error::ClientError::BondingCurveError)?;
898        let min_sol_output = utils::calculate_with_slippage_sell(
899            min_sol_output,
900            slippage_basis_points.unwrap_or(500),
901        );
902
903        let mut instructions = Vec::new();
904
905        // Add sell instruction
906        instructions.push(instructions::sell(
907            &self.payer,
908            &mint,
909            &global_account.fee_recipient,
910            &bonding_curve_account.creator,
911            instructions::Sell {
912                amount,
913                min_sol_output,
914            },
915        ));
916
917        // Close account if balance equals amount
918        #[cfg(feature = "close-ata")]
919        {
920            // Token balance should be guaranteed to be available at this point
921            // due to our fetch logic in the beginning of the function
922            if let Some(balance) = token_balance {
923                // Only close the account if we're selling all tokens
924                if balance == amount {
925                    let token_program = constants::accounts::TOKEN_PROGRAM;
926
927                    // Verify the token account exists before attempting to close it
928                    if self.rpc.get_account(&ata).await.is_ok() {
929                        // Create instruction to close the ATA
930                        let close_instruction = close_account(
931                            &token_program,
932                            &ata,
933                            &self.payer.pubkey(),
934                            &self.payer.pubkey(),
935                            &[&self.payer.pubkey()],
936                        )
937                        .map_err(|err| {
938                            error::ClientError::OtherError(format!(
939                                "Failed to create close account instruction: pubkey={}: {}",
940                                ata, err
941                            ))
942                        })?;
943
944                        instructions.push(close_instruction);
945                    } else {
946                        // Log warning but don't fail the transaction if account doesn't exist
947                        eprintln!(
948                            "Warning: Cannot close token account {}, it doesn't exist",
949                            ata
950                        );
951                    }
952                }
953            } else {
954                // This case should not occur due to our balance fetch logic,
955                // but handle it gracefully just in case
956                eprintln!("Warning: Token balance unavailable, not closing account");
957            }
958        }
959
960        Ok(instructions)
961    }
962
963    /// Gets the Program Derived Address (PDA) for the global state account
964    ///
965    /// Derives the address of the global state account using the program ID and a
966    /// constant seed. The global state account contains program-wide configuration
967    /// such as fee settings and fee recipient.
968    ///
969    /// # Returns
970    ///
971    /// Returns the PDA public key derived from the GLOBAL_SEED
972    ///
973    /// # Examples
974    ///
975    /// ```
976    /// # use pumpfun::PumpFun;
977    /// # use solana_sdk::pubkey::Pubkey;
978    /// #
979    /// let global_pda: Pubkey = PumpFun::get_global_pda();
980    /// println!("Global state account: {}", global_pda);
981    /// ```
982    pub fn get_global_pda() -> Pubkey {
983        let seeds: &[&[u8]; 1] = &[constants::seeds::GLOBAL_SEED];
984        let program_id: &Pubkey = &constants::accounts::PUMPFUN;
985        Pubkey::find_program_address(seeds, program_id).0
986    }
987
988    /// Gets the Program Derived Address (PDA) for the mint authority
989    ///
990    /// Derives the address of the mint authority PDA using the program ID and a
991    /// constant seed. The mint authority PDA is the authority that can mint new
992    /// tokens for any token created through the Pump.fun program.
993    ///
994    /// # Returns
995    ///
996    /// Returns the PDA public key derived from the MINT_AUTHORITY_SEED
997    ///
998    /// # Examples
999    ///
1000    /// ```
1001    /// # use pumpfun::PumpFun;
1002    /// # use solana_sdk::pubkey::Pubkey;
1003    /// #
1004    /// let mint_authority: Pubkey = PumpFun::get_mint_authority_pda();
1005    /// println!("Mint authority account: {}", mint_authority);
1006    /// ```
1007    pub fn get_mint_authority_pda() -> Pubkey {
1008        let seeds: &[&[u8]; 1] = &[constants::seeds::MINT_AUTHORITY_SEED];
1009        let program_id: &Pubkey = &constants::accounts::PUMPFUN;
1010        Pubkey::find_program_address(seeds, program_id).0
1011    }
1012
1013    /// Gets the Program Derived Address (PDA) for a token's bonding curve account
1014    ///
1015    /// Derives the address of a token's bonding curve account using the program ID,
1016    /// a constant seed, and the token mint address. The bonding curve account stores
1017    /// the state and parameters that govern the token's price dynamics.
1018    ///
1019    /// # Arguments
1020    ///
1021    /// * `mint` - Public key of the token mint
1022    ///
1023    /// # Returns
1024    ///
1025    /// Returns Some(PDA) if derivation succeeds, or None if it fails
1026    ///
1027    /// # Examples
1028    ///
1029    /// ```
1030    /// # use pumpfun::PumpFun;
1031    /// # use solana_sdk::{pubkey, pubkey::Pubkey};
1032    /// #
1033    /// let mint = pubkey!("TokenM1ntPubk3yXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
1034    /// if let Some(bonding_curve) = PumpFun::get_bonding_curve_pda(&mint) {
1035    ///     println!("Bonding curve account: {}", bonding_curve);
1036    /// }
1037    /// ```
1038    pub fn get_bonding_curve_pda(mint: &Pubkey) -> Option<Pubkey> {
1039        let seeds: &[&[u8]; 2] = &[constants::seeds::BONDING_CURVE_SEED, mint.as_ref()];
1040        let program_id: &Pubkey = &constants::accounts::PUMPFUN;
1041        let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id);
1042        pda.map(|pubkey| pubkey.0)
1043    }
1044
1045    /// Gets the Program Derived Address (PDA) for a token's metadata account
1046    ///
1047    /// Derives the address of a token's metadata account following the Metaplex Token Metadata
1048    /// standard. The metadata account stores information about the token such as name,
1049    /// symbol, and URI pointing to additional metadata.
1050    ///
1051    /// # Arguments
1052    ///
1053    /// * `mint` - Public key of the token mint
1054    ///
1055    /// # Returns
1056    ///
1057    /// Returns the PDA public key for the token's metadata account
1058    ///
1059    /// # Examples
1060    ///
1061    /// ```
1062    /// # use pumpfun::PumpFun;
1063    /// # use solana_sdk::{pubkey, pubkey::Pubkey};
1064    /// #
1065    /// let mint = pubkey!("TokenM1ntPubk3yXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
1066    /// let metadata_pda = PumpFun::get_metadata_pda(&mint);
1067    /// println!("Token metadata account: {}", metadata_pda);
1068    /// ```
1069    pub fn get_metadata_pda(mint: &Pubkey) -> Pubkey {
1070        let seeds: &[&[u8]; 3] = &[
1071            constants::seeds::METADATA_SEED,
1072            constants::accounts::MPL_TOKEN_METADATA.as_ref(),
1073            mint.as_ref(),
1074        ];
1075        let program_id: &Pubkey = &constants::accounts::MPL_TOKEN_METADATA;
1076        Pubkey::find_program_address(seeds, program_id).0
1077    }
1078
1079    /// Gets the global state account data containing program-wide configuration
1080    ///
1081    /// Fetches and deserializes the global state account which contains program-wide
1082    /// configuration parameters such as:
1083    /// - Fee basis points for trading
1084    /// - Fee recipient account
1085    /// - Bonding curve parameters
1086    /// - Other platform-wide settings
1087    ///
1088    /// # Returns
1089    ///
1090    /// Returns the deserialized GlobalAccount if successful, or a ClientError if the operation fails
1091    ///
1092    /// # Errors
1093    ///
1094    /// Returns an error if:
1095    /// - The account cannot be found on-chain
1096    /// - The account data cannot be properly deserialized
1097    ///
1098    /// # Examples
1099    ///
1100    /// ```no_run
1101    /// # use pumpfun::{PumpFun, common::types::{Cluster, PriorityFee}};
1102    /// # use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair};
1103    /// # use std::sync::Arc;
1104    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1105    /// # let payer = Arc::new(Keypair::new());
1106    /// # let commitment = CommitmentConfig::confirmed();
1107    /// # let cluster = Cluster::devnet(commitment, PriorityFee::default());
1108    /// # let client = PumpFun::new(payer, cluster);
1109    /// let global = client.get_global_account().await?;
1110    /// println!("Fee basis points: {}", global.fee_basis_points);
1111    /// println!("Fee recipient: {}", global.fee_recipient);
1112    /// # Ok(())
1113    /// # }
1114    /// ```
1115    pub async fn get_global_account(&self) -> Result<accounts::GlobalAccount, error::ClientError> {
1116        let global: Pubkey = Self::get_global_pda();
1117
1118        let account = self
1119            .rpc
1120            .get_account(&global)
1121            .await
1122            .map_err(error::ClientError::SolanaClientError)?;
1123
1124        solana_sdk::borsh1::try_from_slice_unchecked::<accounts::GlobalAccount>(&account.data)
1125            .map_err(error::ClientError::BorshError)
1126    }
1127
1128    /// Gets a token's bonding curve account data containing pricing parameters
1129    ///
1130    /// Fetches and deserializes a token's bonding curve account which contains the
1131    /// state and parameters that determine the token's price dynamics, including:
1132    /// - Current supply
1133    /// - Reserve balance
1134    /// - Bonding curve parameters
1135    /// - Other token-specific configuration
1136    ///
1137    /// # Arguments
1138    ///
1139    /// * `mint` - Public key of the token mint
1140    ///
1141    /// # Returns
1142    ///
1143    /// Returns the deserialized BondingCurveAccount if successful, or a ClientError if the operation fails
1144    ///
1145    /// # Errors
1146    ///
1147    /// Returns an error if:
1148    /// - The bonding curve PDA cannot be derived
1149    /// - The account cannot be found on-chain
1150    /// - The account data cannot be properly deserialized
1151    ///
1152    /// # Examples
1153    ///
1154    /// ```no_run
1155    /// # use pumpfun::{PumpFun, common::types::{Cluster, PriorityFee}};
1156    /// # use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair, pubkey};
1157    /// # use std::sync::Arc;
1158    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1159    /// # let payer = Arc::new(Keypair::new());
1160    /// # let commitment = CommitmentConfig::confirmed();
1161    /// # let cluster = Cluster::devnet(commitment, PriorityFee::default());
1162    /// # let client = PumpFun::new(payer, cluster);
1163    /// let mint = pubkey!("TokenM1ntPubk3yXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
1164    /// let bonding_curve = client.get_bonding_curve_account(&mint).await?;
1165    /// println!("Bonding Curve Account: {:#?}", bonding_curve);
1166    /// # Ok(())
1167    /// # }
1168    /// ```
1169    pub async fn get_bonding_curve_account(
1170        &self,
1171        mint: &Pubkey,
1172    ) -> Result<accounts::BondingCurveAccount, error::ClientError> {
1173        let bonding_curve_pda =
1174            Self::get_bonding_curve_pda(mint).ok_or(error::ClientError::BondingCurveNotFound)?;
1175
1176        let account = self
1177            .rpc
1178            .get_account(&bonding_curve_pda)
1179            .await
1180            .map_err(error::ClientError::SolanaClientError)?;
1181
1182        solana_sdk::borsh1::try_from_slice_unchecked::<accounts::BondingCurveAccount>(&account.data)
1183            .map_err(error::ClientError::BorshError)
1184    }
1185
1186    /// Gets the creator vault address (for claiming pump creator fees)
1187    ///
1188    /// Derives the token creator's vault using the program ID,
1189    /// a constant seed, and the creator's address.
1190    ///
1191    /// # Arguments
1192    ///
1193    /// * `creator` - Public key of the token's creator
1194    ///
1195    /// # Returns
1196    ///
1197    /// Returns Some(PDA) if derivation succeeds, or None if it fails
1198    ///
1199    /// # Examples
1200    ///
1201    /// ```
1202    /// # use pumpfun::PumpFun;
1203    /// # use solana_sdk::{pubkey, pubkey::Pubkey};
1204    /// #
1205    /// let creator = pubkey!("Amya8kr2bzEY9kyXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
1206    /// if let Some(bonding_curve) = PumpFun::get_creator_vault_pda(&creator) {
1207    ///     println!("Creator vault address: {}", creator);
1208    /// }
1209    /// ```
1210    pub fn get_creator_vault_pda(creator: &Pubkey) -> Option<Pubkey> {
1211        let seeds: &[&[u8]; 2] = &[constants::seeds::CREATOR_VAULT_SEED, creator.as_ref()];
1212        let program_id: &Pubkey = &constants::accounts::PUMPFUN;
1213        let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id);
1214        pda.map(|pubkey| pubkey.0)
1215    }
1216
1217    /// Returns the PDA of a user volume accumulator account.
1218    ///
1219    /// # Arguments
1220    /// * `user` - Public key of the user.
1221    ///
1222    /// # Returns
1223    /// PDA of the corresponding user volume accumulator account.
1224    pub fn get_user_volume_accumulator_pda(user: &Pubkey) -> Pubkey {
1225        let (user_volume_accumulator, _bump) = Pubkey::find_program_address(
1226            &[b"user_volume_accumulator", user.as_ref()],
1227            &constants::accounts::PUMPFUN,
1228        );
1229        user_volume_accumulator
1230    }
1231}