1use crate::jito::{get_jito_tip_pubkey, send_jito_tx};
2use crate::util::apply_fee;
3use log::{debug, error, info, warn};
4use solana_account_decoder::UiAccountEncoding;
5use solana_client::rpc_config::{
6 RpcAccountInfoConfig, RpcSendTransactionConfig, RpcTransactionConfig,
7};
8use solana_sdk::hash::Hash;
9use solana_sdk::system_instruction::transfer;
10use solana_sdk::transaction::{Transaction, VersionedTransaction};
11use std::error::Error;
12
13use std::str::FromStr;
14use tokio::time::{sleep, Duration};
15
16use borsh::{BorshDeserialize, BorshSerialize};
17
18use serde::{Deserialize, Serialize};
19use solana_client::nonblocking::rpc_client::RpcClient;
20use solana_sdk::commitment_config::{CommitmentConfig, CommitmentLevel};
21use solana_sdk::instruction::{AccountMeta, Instruction};
22use solana_sdk::pubkey::Pubkey;
23use solana_sdk::signature::{Keypair, Signature};
24use solana_sdk::signer::Signer;
25use solana_transaction_status::{
26 EncodedConfirmedTransactionWithStatusMeta, EncodedTransaction, UiMessage,
27 UiParsedMessage, UiTransactionEncoding,
28};
29
30use crate::constants::{
31 ASSOCIATED_TOKEN_PROGRAM, EVENT_AUTHORITY, PUMP_BUY_METHOD,
32 PUMP_FEE_ADDRESS, PUMP_FUN_PROGRAM, PUMP_GLOBAL_ADDRESS, PUMP_SELL_METHOD,
33 RENT_PROGRAM, SYSTEM_PROGRAM_ID, TOKEN_PROGRAM,
34};
35use crate::util::{
36 make_compute_budget_ixs, pubkey_to_string,
37 string_to_pubkey, };
39
40#[derive(BorshSerialize)]
41pub struct PumpFunSwapInstructionData {
42 pub method_id: [u8; 8],
43 pub token_amount: u64,
44 pub lamports: u64,
45}
46
47#[derive(BorshSerialize, BorshDeserialize, Debug, Clone)]
48pub struct BondingCurveLayout {
49 pub blob1: u64,
50 pub virtual_token_reserves: u64,
51 pub virtual_sol_reserves: u64,
52 pub real_token_reserves: u64,
53 pub real_sol_reserves: u64,
54 pub blob4: u64,
55 pub complete: bool,
56}
57
58#[derive(Serialize, Deserialize, Debug, Clone)]
59pub struct PumpTokenData {
60 pub address: String,
61 pub balance: u64,
62 pub image_uri: String,
63 pub market_cap: f64,
64 pub mint: String,
65 pub name: String,
66 pub symbol: String,
67 pub value: f64,
68}
69
70impl BondingCurveLayout {
71 pub const LEN: usize = 8 + 8 + 8 + 8 + 8 + 8 + 1;
72
73 pub fn parse(data: &[u8]) -> Result<Self, std::io::Error> {
74 Self::try_from_slice(data)
75 }
76}
77
78pub async fn get_slot_created(
79 rpc_client: &RpcClient,
80 mint: &Pubkey,
81) -> Result<u64, Box<dyn std::error::Error>> {
82 let token_transactions =
83 rpc_client.get_signatures_for_address(mint).await?;
84
85 if token_transactions.is_empty() {
86 warn!("No transactions found for mint: {}", mint);
87 return Ok(0);
88 }
89
90 let first_tx_sig = token_transactions.last().unwrap();
91 let first_tx = rpc_client
92 .get_transaction_with_config(
93 &Signature::from_str(&first_tx_sig.signature).expect("signature"),
94 RpcTransactionConfig {
95 encoding: Some(UiTransactionEncoding::Json),
96 commitment: None,
97 max_supported_transaction_version: Some(0),
98 },
99 )
100 .await?;
101
102 Ok(first_tx.slot)
103}
104
105pub fn mint_to_pump_accounts(mint: &Pubkey) -> PumpAccounts {
106 let (bonding_curve, _) = Pubkey::find_program_address(
108 &[b"bonding-curve", mint.as_ref()],
109 &Pubkey::from_str(PUMP_FUN_PROGRAM).unwrap(),
110 );
111
112 let associated_bonding_curve =
114 spl_associated_token_account::get_associated_token_address(
115 &bonding_curve,
116 mint,
117 );
118
119 PumpAccounts {
120 mint: *mint,
121 bonding_curve,
122 associated_bonding_curve,
123 dev: Pubkey::default(),
124 metadata: Pubkey::default(),
125 }
126}
127pub async fn get_bonding_curve(
128 rpc_client: &RpcClient,
129 bonding_curve_pubkey: Pubkey,
130) -> Result<BondingCurveLayout, Box<dyn Error>> {
131 const MAX_RETRIES: u32 = 5;
132 const INITIAL_DELAY_MS: u64 = 200;
133 let mut retries = 0;
134 let mut delay = Duration::from_millis(INITIAL_DELAY_MS);
135
136 loop {
137 match rpc_client
138 .get_account_with_config(
139 &bonding_curve_pubkey,
140 RpcAccountInfoConfig {
141 encoding: Some(UiAccountEncoding::Base64),
142 commitment: Some(CommitmentConfig::processed()),
143 data_slice: None,
144 min_context_slot: None,
145 },
146 )
147 .await
148 {
149 Ok(res) => {
150 if let Some(account) = res.value {
151 let data_length = account.data.len();
153 let data: [u8; 49] =
154 account.data.try_into().map_err(|_| {
155 format!("Invalid data length: {}", data_length)
156 })?;
157
158 debug!("Raw bytes: {:?}", data);
159
160 let layout = BondingCurveLayout {
161 blob1: u64::from_le_bytes(data[0..8].try_into()?),
162 virtual_token_reserves: u64::from_le_bytes(
163 data[8..16].try_into()?,
164 ),
165 virtual_sol_reserves: u64::from_le_bytes(
166 data[16..24].try_into()?,
167 ),
168 real_token_reserves: u64::from_le_bytes(
169 data[24..32].try_into()?,
170 ),
171 real_sol_reserves: u64::from_le_bytes(
172 data[32..40].try_into()?,
173 ),
174 blob4: u64::from_le_bytes(data[40..48].try_into()?),
175 complete: data[48] != 0,
176 };
177
178 debug!("Parsed BondingCurveLayout: {:?}", layout);
179 return Ok(layout);
180 } else {
181 if retries >= MAX_RETRIES {
182 error!("Max retries reached. Account not found.");
183 return Err(
184 "Account not found after max retries".into()
185 );
186 }
187 warn!(
188 "Attempt {} failed: Account not found. Retrying in {:?}...",
189 retries + 1,
190 delay
191 );
192 sleep(delay).await;
193 retries += 1;
194 delay = Duration::from_millis(
195 INITIAL_DELAY_MS * 2u64.pow(retries),
196 );
197 continue;
198 }
199 }
200 Err(e) => {
201 if retries >= MAX_RETRIES {
202 error!("Max retries reached. Last error: {}", e);
203 return Err(format!(
204 "Max retries reached. Last error: {}",
205 e
206 )
207 .into());
208 }
209 warn!(
210 "Attempt {} failed: {}. Retrying in {:?}...",
211 retries + 1,
212 e,
213 delay
214 );
215 sleep(delay).await;
216 retries += 1;
217 delay = Duration::from_millis(
218 INITIAL_DELAY_MS * 2u64.pow(retries),
219 );
220 }
221 }
222 }
223}
224
225pub fn get_pump_token_amount(
226 virtual_sol_reserves: u64,
227 virtual_token_reserves: u64,
228 real_token_reserves: Option<u64>,
229 lamports: u64,
230) -> Result<u64, Box<dyn std::error::Error>> {
231 let virtual_sol_reserves = virtual_sol_reserves as u128;
232 let virtual_token_reserves = virtual_token_reserves as u128;
233 let amount_in = lamports as u128;
234
235 let reserves_product = virtual_sol_reserves
237 .checked_mul(virtual_token_reserves)
238 .ok_or("Overflow in reserves product calculation")?;
239
240 let new_virtual_sol_reserve = virtual_sol_reserves
241 .checked_add(amount_in)
242 .ok_or("Overflow in new virtual SOL reserve calculation")?;
243
244 let new_virtual_token_reserve = reserves_product
245 .checked_div(new_virtual_sol_reserve)
246 .ok_or("Division by zero or overflow in new virtual token reserve calculation")?
247 .checked_add(1)
248 .ok_or("Overflow in new virtual token reserve calculation")?;
249
250 let amount_out = virtual_token_reserves
251 .checked_sub(new_virtual_token_reserve)
252 .ok_or("Underflow in amount out calculation")?;
253
254 let final_amount_out =
255 if let Some(real_token_reserves) = real_token_reserves {
256 std::cmp::min(amount_out, real_token_reserves as u128)
257 } else {
258 amount_out
259 };
260
261 Ok(final_amount_out as u64)
262}
263
264#[derive(Debug, Clone, Deserialize, Serialize)]
265pub struct PumpBuyRequest {
266 #[serde(
267 serialize_with = "pubkey_to_string",
268 deserialize_with = "string_to_pubkey"
269 )]
270 pub mint: Pubkey,
271 #[serde(
272 serialize_with = "pubkey_to_string",
273 deserialize_with = "string_to_pubkey"
274 )]
275 pub bonding_curve: Pubkey,
276 #[serde(
277 serialize_with = "pubkey_to_string",
278 deserialize_with = "string_to_pubkey"
279 )]
280 pub associated_bonding_curve: Pubkey,
281 pub virtual_token_reserves: u64,
285 pub virtual_sol_reserves: u64,
287
288 pub slot: Option<u64>,
289}
290
291pub async fn buy_pump_token(
292 wallet: &Keypair,
293 latest_blockhash: Hash,
294 pump_accounts: PumpAccounts,
295 token_amount: u64,
296 lamports: u64,
297 tip: u64,
298) -> Result<(), Box<dyn Error>> {
299 let owner = wallet.pubkey();
300
301 info!("{} buying {} {}", owner, token_amount, pump_accounts.mint);
302
303 let mut ixs = _make_buy_ixs(
304 owner,
305 pump_accounts.mint,
306 pump_accounts.bonding_curve,
307 pump_accounts.associated_bonding_curve,
308 token_amount,
309 apply_fee(lamports),
310 )?;
311
312 ixs.push(transfer(&owner, &get_jito_tip_pubkey(), tip));
313
314 let tx = Transaction::new_signed_with_payer(
315 &ixs,
316 Some(&owner),
317 &[wallet],
318 latest_blockhash,
319 );
320
321 send_jito_tx(tx).await?;
322
323 Ok(())
324}
325
326pub fn _make_buy_ixs(
327 owner: Pubkey,
328 mint: Pubkey,
329 bonding_curve: Pubkey,
330 associated_bonding_curve: Pubkey,
331 token_amount: u64,
332 lamports: u64,
333) -> Result<Vec<Instruction>, Box<dyn Error>> {
334 let mut ixs = vec![];
335 let ata = spl_associated_token_account::get_associated_token_address(
336 &owner, &mint,
337 );
338
339 ixs.push(
340 spl_associated_token_account::instruction::create_associated_token_account_idempotent(
341 &owner,
342 &owner,
343 &mint,
344 &spl_token::id(),
345 ),
346 );
347 ixs.push(make_pump_swap_ix(
348 owner,
349 mint,
350 bonding_curve,
351 associated_bonding_curve,
352 token_amount,
353 lamports,
354 ata,
355 )?);
356
357 Ok(ixs)
358}
359
360async fn _send_tx_standard(
361 ixs: Vec<Instruction>,
362 wallet: &Keypair,
363 rpc_client: &RpcClient,
364 owner: Pubkey,
365) -> Result<(), Box<dyn Error>> {
366 let transaction =
367 VersionedTransaction::from(Transaction::new_signed_with_payer(
368 &ixs,
369 Some(&owner),
370 &[wallet],
371 rpc_client.get_latest_blockhash().await?,
372 ));
373 let res = rpc_client
374 .send_transaction_with_config(
375 &transaction,
376 RpcSendTransactionConfig {
377 skip_preflight: true,
378 min_context_slot: None,
379 preflight_commitment: Some(CommitmentLevel::Processed),
380 max_retries: None,
381 encoding: None,
382 },
383 )
384 .await;
385
386 match res {
387 Ok(sig) => {
388 info!("Transaction sent: {}", sig);
389 }
390 Err(e) => {
391 return Err(e.into());
392 }
393 }
394
395 Ok(())
396}
397
398#[timed::timed(duration(printer = "info!"))]
399pub async fn sell_pump_token(
400 wallet: &Keypair,
401 latest_blockhash: Hash,
402 pump_accounts: PumpAccounts,
403 token_amount: u64,
404) -> Result<(), Box<dyn Error>> {
405 let owner = wallet.pubkey();
406
407 let ata = spl_associated_token_account::get_associated_token_address(
408 &owner,
409 &pump_accounts.mint,
410 );
411
412 let mut ixs = vec![];
413 let mut compute_budget_ixs = make_compute_budget_ixs(69_000, 69_000);
414 let sell_ix = make_pump_sell_ix(owner, pump_accounts, token_amount, ata)?;
415 ixs.append(&mut compute_budget_ixs);
416 ixs.push(sell_ix);
417 ixs.push(transfer(&owner, &get_jito_tip_pubkey(), 30_000));
418
419 let tx = Transaction::new_signed_with_payer(
420 &ixs,
421 Some(&owner),
422 &[wallet],
423 latest_blockhash,
424 );
425
426 send_jito_tx(tx).await?;
427
428 Ok(())
429}
430
431pub fn make_pump_sell_ix(
445 owner: Pubkey,
446 pump_accounts: PumpAccounts,
447 token_amount: u64,
448 ata: Pubkey,
449) -> Result<Instruction, Box<dyn Error>> {
450 let accounts: [AccountMeta; 12] = [
451 AccountMeta::new_readonly(
452 Pubkey::from_str(PUMP_GLOBAL_ADDRESS)?,
453 false,
454 ),
455 AccountMeta::new(Pubkey::from_str(PUMP_FEE_ADDRESS)?, false),
456 AccountMeta::new_readonly(pump_accounts.mint, false),
457 AccountMeta::new(pump_accounts.bonding_curve, false),
458 AccountMeta::new(pump_accounts.associated_bonding_curve, false),
459 AccountMeta::new(ata, false),
460 AccountMeta::new(owner, true),
461 AccountMeta::new_readonly(Pubkey::from_str(SYSTEM_PROGRAM_ID)?, false),
462 AccountMeta::new_readonly(
463 Pubkey::from_str(ASSOCIATED_TOKEN_PROGRAM)?,
464 false,
465 ),
466 AccountMeta::new_readonly(Pubkey::from_str(TOKEN_PROGRAM)?, false),
467 AccountMeta::new_readonly(Pubkey::from_str(EVENT_AUTHORITY)?, false),
468 AccountMeta::new_readonly(Pubkey::from_str(PUMP_FUN_PROGRAM)?, false),
469 ];
470
471 let data = PumpFunSwapInstructionData {
473 method_id: PUMP_SELL_METHOD,
474 token_amount,
475 lamports: 0,
476 };
477
478 Ok(Instruction::new_with_borsh(
479 Pubkey::from_str(PUMP_FUN_PROGRAM)?,
480 &data,
481 accounts.to_vec(),
482 ))
483}
484
485pub fn make_pump_swap_ix(
500 owner: Pubkey,
501 mint: Pubkey,
502 bonding_curve: Pubkey,
503 associated_bonding_curve: Pubkey,
504 token_amount: u64,
505 lamports: u64,
506 ata: Pubkey,
507) -> Result<Instruction, Box<dyn Error>> {
508 let accounts: [AccountMeta; 12] = [
509 AccountMeta::new_readonly(
510 Pubkey::from_str(PUMP_GLOBAL_ADDRESS)?,
511 false,
512 ),
513 AccountMeta::new(Pubkey::from_str(PUMP_FEE_ADDRESS)?, false),
514 AccountMeta::new_readonly(mint, false),
515 AccountMeta::new(bonding_curve, false),
516 AccountMeta::new(associated_bonding_curve, false),
517 AccountMeta::new(ata, false),
518 AccountMeta::new(owner, true),
519 AccountMeta::new_readonly(Pubkey::from_str(SYSTEM_PROGRAM_ID)?, false),
520 AccountMeta::new_readonly(Pubkey::from_str(TOKEN_PROGRAM)?, false),
521 AccountMeta::new_readonly(Pubkey::from_str(RENT_PROGRAM)?, false),
522 AccountMeta::new_readonly(Pubkey::from_str(EVENT_AUTHORITY)?, false),
523 AccountMeta::new_readonly(Pubkey::from_str(PUMP_FUN_PROGRAM)?, false),
524 ];
525
526 let data = PumpFunSwapInstructionData {
527 method_id: PUMP_BUY_METHOD,
528 token_amount,
529 lamports,
530 };
531
532 Ok(Instruction::new_with_borsh(
533 Pubkey::from_str(PUMP_FUN_PROGRAM)?,
534 &data,
535 accounts.to_vec(),
536 ))
537}
538
539#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
540pub struct PumpAccounts {
541 #[serde(
542 serialize_with = "pubkey_to_string",
543 deserialize_with = "string_to_pubkey"
544 )]
545 pub mint: Pubkey,
546 #[serde(
547 serialize_with = "pubkey_to_string",
548 deserialize_with = "string_to_pubkey"
549 )]
550 pub bonding_curve: Pubkey,
551 #[serde(
552 serialize_with = "pubkey_to_string",
553 deserialize_with = "string_to_pubkey"
554 )]
555 pub associated_bonding_curve: Pubkey,
556 #[serde(
557 serialize_with = "pubkey_to_string",
558 deserialize_with = "string_to_pubkey"
559 )]
560 pub dev: Pubkey,
561 #[serde(
562 serialize_with = "pubkey_to_string",
563 deserialize_with = "string_to_pubkey"
564 )]
565 pub metadata: Pubkey,
566}
567
568pub fn parse_pump_accounts(
569 tx: EncodedConfirmedTransactionWithStatusMeta,
570) -> Result<PumpAccounts, Box<dyn Error>> {
571 if let EncodedTransaction::Json(tx) = &tx.transaction.transaction {
572 if let UiMessage::Parsed(UiParsedMessage {
573 account_keys,
574 instructions: _,
575 recent_blockhash: _,
576 address_table_lookups: _,
577 }) = &tx.message
578 {
579 debug!("Account keys: {:?}", account_keys);
580 if account_keys.len() >= 5 {
581 let dev = account_keys[0].pubkey.parse()?;
582 let mint = account_keys[1].pubkey.parse()?;
583 let bonding_curve = account_keys[3].pubkey.parse()?;
584 let associated_bonding_curve =
585 account_keys[4].pubkey.parse()?;
586 let metadata = account_keys[5].pubkey.parse()?;
587
588 Ok(PumpAccounts {
589 mint,
590 bonding_curve,
591 associated_bonding_curve,
592 dev,
593 metadata,
594 })
595 } else {
596 Err("Not enough account keys".into())
597 }
598 } else {
599 Err("Not a parsed transaction".into())
600 }
601 } else {
602 Err("Not a JSON transaction".into())
603 }
604}
605
606#[derive(Serialize, Deserialize, Debug)]
607pub struct PumpTokenInfo {
608 pub associated_bonding_curve: String,
609 pub bonding_curve: String,
610 pub complete: bool,
611 pub created_timestamp: i64,
612 pub creator: String,
613 pub description: String,
614 pub image_uri: String,
615 pub inverted: bool,
616 pub is_currently_live: bool,
617 pub king_of_the_hill_timestamp: i64,
618 pub last_reply: i64,
619 pub market_cap: f64,
620 pub market_id: String,
621 pub metadata_uri: String,
622 pub mint: String,
623 pub name: String,
624 pub nsfw: bool,
625 pub profile_image: Option<String>,
626 pub raydium_pool: String,
627 pub reply_count: i32,
628 pub show_name: bool,
629 pub symbol: String,
630 pub telegram: Option<String>,
631 pub total_supply: i64,
632 pub twitter: Option<String>,
633 pub usd_market_cap: f64,
634 pub username: Option<String>,
635 pub virtual_sol_reserves: i64,
636 pub virtual_token_reserves: i64,
637 pub website: Option<String>,
638}
639
640#[derive(Serialize, Deserialize, Debug)]
641pub struct IPFSMetadata {
642 pub name: String,
643 pub symbol: String,
644 pub description: String,
645 pub image: String,
646 #[serde(rename = "showName")]
647 pub show_name: Option<bool>,
648 #[serde(rename = "createdOn")]
649 pub created_on: Option<String>,
650 pub twitter: Option<String>,
651 pub telegram: Option<String>,
652 pub website: Option<String>,
653}
654
655pub async fn fetch_metadata(
656 mint: &Pubkey,
657) -> Result<PumpTokenInfo, Box<dyn Error>> {
658 const MAX_RETRIES: u32 = 3;
659 const INITIAL_DELAY_MS: u64 = 100;
660
661 let mut retry_count = 0;
662 let mut delay_ms = INITIAL_DELAY_MS;
663
664 loop {
665 match fetch_metadata_inner(mint).await {
666 Ok(metadata) => {
667 info!("Metadata fetched successfully");
668 return Ok(metadata);
669 }
670 Err(e) => {
671 if retry_count >= MAX_RETRIES {
672 info!("Failed to fetch metadata after all retries");
673 return Err(e);
674 }
675 info!(
676 "Retry attempt {} failed: {:?}. Retrying in {} ms...",
677 retry_count + 1,
678 e,
679 delay_ms
680 );
681 tokio::time::sleep(Duration::from_millis(delay_ms)).await;
682 retry_count += 1;
683 delay_ms *= 2; }
685 }
686 }
687}
688
689async fn fetch_metadata_inner(
690 mint: &Pubkey,
691) -> Result<PumpTokenInfo, Box<dyn Error>> {
692 let url = format!("https://frontend-api.pump.fun/coins/{}", mint);
693 info!("Fetching metadata from: {}", url);
694 let res = reqwest::get(&url).await?;
695 info!("res: {:?}", res);
696 let data = res.json::<PumpTokenInfo>().await?;
697 Ok(data)
698}
699
700#[cfg(test)]
701mod tests {
702 use solana_sdk::signer::EncodableKey;
703
704 use crate::util::env;
705
706 use super::*;
707
708 #[tokio::test]
709 async fn test_fetch_metadata() {
710 let metadata = fetch_metadata(
711 &Pubkey::from_str("4cRkQ2dntpusYag6Zmvco8T78WxK9Jqh1eEZJox8pump")
712 .expect("parse mint"),
713 )
714 .await
715 .expect("fetch_metadata");
716
717 assert_eq!(metadata.name, "🗿".to_string());
718 assert_eq!(metadata.symbol, "🗿".to_string());
719 assert_eq!(
720 metadata.image_uri,
721 "https://cf-ipfs.com/ipfs/QmXn5xkUMxNQ5c5Sfct8rFTq9jNi6jsSHm1yLY2nQyeSke".to_string()
722 );
723 assert_eq!(
724 metadata.twitter,
725 Some("https://x.com/thefirstgigasol".to_string())
726 );
727 assert_eq!(
728 metadata.telegram,
729 Some("https://t.me/+keptGgOKxN45YWRl".to_string())
730 );
731 assert_eq!(
732 metadata.website,
733 Some("https://thefirstgiga.com/".to_string())
734 );
735 }
736
737 #[test]
738 fn test_parse_pump_accounts() {
739 let sample_tx = std::fs::read_to_string("mocks/pump_fun_tx.json")
740 .expect("read tx");
741 let tx: EncodedConfirmedTransactionWithStatusMeta =
742 serde_json::from_str(&sample_tx).expect("parse tx");
743 let accounts = parse_pump_accounts(tx).expect("parse accounts");
744 println!("{:?}", accounts);
745 assert!(
746 accounts.mint.to_string()
747 == "6kPvKNrLqg23mApAvHzMKWohhVdSrA54HvrpYud8pump"
748 );
749 assert!(
750 accounts.bonding_curve.to_string()
751 == "6TGz5VAFF6UpSmTSk9327utugSWJCyVeVVFXDtZnMtNp"
752 );
753 assert!(
754 accounts.associated_bonding_curve.to_string()
755 == "4VwNGUif2ubbPjx4YNHmxEH7L4Yt2QFeo8uVTrVC3F68"
756 );
757 assert!(
758 accounts.dev.to_string()
759 == "2wgo94ZaiUNUkFBSKNaKsUgEANgSdex7gRpFKR39DPzw"
760 );
761 }
762
763 #[tokio::test]
764 #[ignore]
765 async fn test_buy_pump_token() {
766 dotenv::from_filename(".env").unwrap();
767 let lamports = 690000;
769 let pump_accounts = PumpAccounts {
770 mint: Pubkey::from_str(
771 "5KEDcNGebCcLptWzknqVmPRNLHfiHA9Mm2djVE26pump",
772 )
773 .expect("parse mint"),
774 bonding_curve: Pubkey::from_str(
775 "Drhj4djqLsPyiA9qK2YmBngteFba8XhhvuQoBToW6pMS",
776 )
777 .expect("parse bonding curve"),
778 associated_bonding_curve: Pubkey::from_str(
779 "7uXq8diH862Dh8NgMHt5Tzsai8SvURhH58rArgxvs7o1",
780 )
781 .expect("parse associated bonding curve"),
782 dev: Pubkey::from_str(
783 "Gizxxed4uXCzL7Q8DyALDVoEEDfMkSV7XyUNrPDnPJ9J",
784 )
785 .expect("parse associated user"),
786 metadata: Pubkey::default(), };
788 let wallet = Keypair::read_from_file(env("FUND_KEYPAIR_PATH"))
789 .expect("read wallet");
790 let rpc_client =
791 RpcClient::new("https://api.mainnet-beta.solana.com".to_string());
792 let tip = 50_000;
793 buy_pump_token(
794 &wallet,
795 rpc_client
796 .get_latest_blockhash()
797 .await
798 .expect("get blockhash"),
799 pump_accounts,
800 100_000,
801 lamports,
802 tip,
803 )
804 .await
805 .expect("buy pump token");
806 }
807
808 #[tokio::test]
809 async fn test_get_bonding_curve_incomplete() {
810 let rpc_client =
811 RpcClient::new("https://api.mainnet-beta.solana.com".to_string());
812 let bonding_curve_pubkey = Pubkey::from_str(
813 "Drhj4djqLsPyiA9qK2YmBngteFba8XhhvuQoBToW6pMS", )
815 .expect("parse bonding curve");
816
817 let bonding_curve =
818 get_bonding_curve(&rpc_client, bonding_curve_pubkey)
819 .await
820 .expect("get bonding curve");
821
822 println!("{:?}", bonding_curve);
823
824 assert!(!bonding_curve.complete);
825 assert_ne!(bonding_curve.virtual_token_reserves, 0);
826 assert_ne!(bonding_curve.virtual_sol_reserves, 0);
827 assert_ne!(bonding_curve.real_token_reserves, 0);
828 }
829
830 #[tokio::test]
831 async fn test_get_bonding_curve_complete() {
832 let rpc_client =
833 RpcClient::new("https://api.mainnet-beta.solana.com".to_string());
834 let bonding_curve_pubkey = Pubkey::from_str(
835 "EB5tQ64HwNjaEoKKYAPkZqndwbULX249EuWSnkjfvR3y", )
837 .expect("parse bonding curve");
838
839 let bonding_curve =
840 get_bonding_curve(&rpc_client, bonding_curve_pubkey)
841 .await
842 .expect("get bonding curve");
843
844 println!("{:?}", bonding_curve);
845
846 assert!(bonding_curve.complete);
847 assert_eq!(bonding_curve.virtual_token_reserves, 0);
848 assert_eq!(bonding_curve.virtual_sol_reserves, 0);
849 assert_eq!(bonding_curve.real_token_reserves, 0);
850 }
851
852 #[tokio::test]
853 async fn test_get_pump_token_amount() {
854 let bonding_curve = BondingCurveLayout {
856 blob1: 6966180631402821399,
857 virtual_token_reserves: 1072964268463317,
858 virtual_sol_reserves: 30000999057,
859 real_token_reserves: 793064268463317,
860 real_sol_reserves: 999057,
861 blob4: 1000000000000000,
862 complete: false,
863 };
864 let lamports = 500000;
865 let expected_token_amount = 17852389307u64;
866 let token_amount = get_pump_token_amount(
867 bonding_curve.virtual_sol_reserves,
868 bonding_curve.virtual_token_reserves,
869 Some(bonding_curve.real_token_reserves),
870 lamports,
871 )
872 .expect("get token amount");
873 let low_thresh = 0.9 * expected_token_amount as f64;
876 let high_thresh = 1.1 * expected_token_amount as f64;
877 let token_amount = token_amount as f64;
878 assert!(token_amount >= low_thresh);
879 assert!(token_amount <= high_thresh);
880 }
881}