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}