solana-trading-sdk 1.0.6

A Rust SDK for trading on Solana
Documentation
use super::{
    amm_calc::{amm_buy_get_token_out, calculate_with_slippage_buy},
    dex_traits::DexTrait,
    pumpfun_common_types::{BuyInfo, SellInfo},
    pumpfun_types::*,
    types::{Create, PoolInfo, SwapInfo},
};
use crate::{common::trading_endpoint::TradingEndpoint, instruction::builder::PriorityFee};
use borsh::BorshSerialize;
use once_cell::sync::OnceCell;
use solana_sdk::{
    instruction::{AccountMeta, Instruction},
    pubkey::Pubkey,
    signature::{Keypair, Signature},
    signer::Signer,
};
use spl_associated_token_account::{get_associated_token_address, instruction::create_associated_token_account};
use std::sync::Arc;

pub struct Pumpfun {
    pub endpoint: Arc<TradingEndpoint>,
    pub global_account: OnceCell<Arc<GlobalAccount>>,
}

#[async_trait::async_trait]
impl DexTrait for Pumpfun {
    async fn initialize(&self) -> anyhow::Result<()> {
        let account = self.endpoint.rpc.get_account(&PUBKEY_GLOBAL_ACCOUNT).await?;
        let global_account = bincode::deserialize::<GlobalAccount>(&account.data)?;
        let global_account = Arc::new(global_account);

        self.global_account.set(global_account).unwrap();
        Ok(())
    }

    fn initialized(&self) -> anyhow::Result<()> {
        if self.global_account.get().is_none() {
            return Err(anyhow::anyhow!("Pumpfun not initialized"));
        }
        Ok(())
    }

    fn get_trading_endpoint(&self) -> Arc<TradingEndpoint> {
        self.endpoint.clone()
    }

    fn use_wsol(&self) -> bool {
        false
    }

    async fn get_pool(&self, mint: &Pubkey) -> anyhow::Result<PoolInfo> {
        let bonding_curve_pda = Self::get_bonding_curve_pda(mint).unwrap();
        let account = self.endpoint.rpc.get_account(&bonding_curve_pda).await?;
        if account.data.is_empty() {
            return Err(anyhow::anyhow!("Bonding curve not found: {}", mint.to_string()));
        }

        let bonding_curve = bincode::deserialize::<BondingCurveAccount>(&account.data)?;
        let creator_vault = Self::get_creator_vault_pda(&bonding_curve.creator)?;

        Ok(PoolInfo {
            pool: bonding_curve_pda,
            creator: Some(bonding_curve.creator),
            creator_vault: Some(creator_vault),
            config: None,
            extra_address: Some(creator_vault),
            token_reserves: bonding_curve.virtual_token_reserves,
            sol_reserves: bonding_curve.virtual_sol_reserves,
        })
    }

    async fn create(&self, payer: Keypair, create: Create, fee: Option<PriorityFee>, tip: Option<u64>) -> anyhow::Result<Vec<Signature>> {
        let mint = create.mint_private_key.pubkey();
        let buy_sol_amount = create.buy_sol_amount;
        let slippage_basis_points = create.slippage_basis_points.unwrap_or(0);

        let create_info = CreateInfo::from_create(&create, payer.pubkey());
        let mut buffer = Vec::new();
        create_info.serialize(&mut buffer)?;

        let blockhash = self.endpoint.rpc.get_latest_blockhash().await?;
        let bonding_curve = Self::get_bonding_curve_pda(&mint)?;

        let mut instructions = vec![];
        let create_instruction = Instruction::new_with_bytes(
            PUBKEY_PUMPFUN,
            &buffer,
            vec![
                AccountMeta::new(mint, true),
                AccountMeta::new(*PUBKEY_MINT_AUTHORITY_PDA, false),
                AccountMeta::new(bonding_curve, false),
                AccountMeta::new(get_associated_token_address(&bonding_curve, &mint), false),
                AccountMeta::new_readonly(*PUBKEY_GLOBAL_PDA, false),
                AccountMeta::new_readonly(mpl_token_metadata::ID, false),
                AccountMeta::new(mpl_token_metadata::accounts::Metadata::find_pda(&mint).0, false),
                AccountMeta::new(payer.pubkey(), true),
                AccountMeta::new_readonly(solana_program::system_program::ID, false),
                AccountMeta::new_readonly(spl_token::ID, false),
                AccountMeta::new_readonly(spl_associated_token_account::ID, false),
                AccountMeta::new_readonly(solana_program::sysvar::rent::ID, false),
                AccountMeta::new_readonly(PUBKEY_EVENT_AUTHORITY, false),
                AccountMeta::new_readonly(PUBKEY_PUMPFUN, false),
            ],
        );

        instructions.push(create_instruction);

        if let Some(buy_sol_amount) = buy_sol_amount {
            let create_ata = create_associated_token_account(&payer.pubkey(), &payer.pubkey(), &mint, &spl_token::ID);
            instructions.push(create_ata);

            let buy_token_amount = amm_buy_get_token_out(INITIAL_VIRTUAL_SOL_RESERVES, INITIAL_VIRTUAL_TOKEN_RESERVES, buy_sol_amount);
            let sol_lamports_with_slippage = calculate_with_slippage_buy(buy_sol_amount, slippage_basis_points);
            let creator_vault = Self::get_creator_vault_pda(&payer.pubkey()).unwrap();
            let buy_instruction = self.build_buy_instruction(
                &payer,
                &mint,
                Some(&creator_vault),
                SwapInfo {
                    token_amount: buy_token_amount,
                    sol_amount: sol_lamports_with_slippage,
                },
            )?;
            instructions.push(buy_instruction);
        }

        let signatures = self
            .endpoint
            .build_and_broadcast_tx(&payer, instructions, blockhash, fee, tip, Some(vec![&create.mint_private_key]))?;

        Ok(signatures)
    }

    fn build_buy_instruction(&self, payer: &Keypair, mint: &Pubkey, creator_vault: Option<&Pubkey>, buy: SwapInfo) -> anyhow::Result<Instruction> {
        self.initialized()?;

        let buy_info: BuyInfo = buy.into();
        let buffer = buy_info.to_buffer()?;
        let bonding_curve = Self::get_bonding_curve_pda(mint)?;

        Ok(Instruction::new_with_bytes(
            PUBKEY_PUMPFUN,
            &buffer,
            vec![
                AccountMeta::new_readonly(PUBKEY_GLOBAL_ACCOUNT, false),
                AccountMeta::new(PUBKEY_FEE_RECIPIENT, false),
                AccountMeta::new_readonly(*mint, false),
                AccountMeta::new(bonding_curve, false),
                AccountMeta::new(get_associated_token_address(&bonding_curve, mint), false),
                AccountMeta::new(get_associated_token_address(&payer.pubkey(), mint), false),
                AccountMeta::new(payer.pubkey(), true),
                AccountMeta::new_readonly(solana_program::system_program::ID, false),
                AccountMeta::new_readonly(spl_token::ID, false),
                AccountMeta::new(*creator_vault.ok_or(anyhow::anyhow!("Creator vault not provided"))?, false),
                AccountMeta::new_readonly(PUBKEY_EVENT_AUTHORITY, false),
                AccountMeta::new_readonly(PUBKEY_PUMPFUN, false),
            ],
        ))
    }

    fn build_sell_instruction(&self, payer: &Keypair, mint: &Pubkey, creator_vault: Option<&Pubkey>, sell: SwapInfo) -> anyhow::Result<Instruction> {
        self.initialized()?;

        let sell_info: SellInfo = sell.into();
        let buffer = sell_info.to_buffer()?;
        let bonding_curve = Self::get_bonding_curve_pda(mint)?;

        Ok(Instruction::new_with_bytes(
            PUBKEY_PUMPFUN,
            &buffer,
            vec![
                AccountMeta::new_readonly(PUBKEY_GLOBAL_ACCOUNT, false),
                AccountMeta::new(PUBKEY_FEE_RECIPIENT, false),
                AccountMeta::new_readonly(*mint, false),
                AccountMeta::new(bonding_curve, false),
                AccountMeta::new(get_associated_token_address(&bonding_curve, mint), false),
                AccountMeta::new(get_associated_token_address(&payer.pubkey(), mint), false),
                AccountMeta::new(payer.pubkey(), true),
                AccountMeta::new_readonly(solana_program::system_program::ID, false),
                AccountMeta::new(*creator_vault.ok_or(anyhow::anyhow!("Creator vault not provided"))?, false),
                AccountMeta::new_readonly(spl_token::ID, false),
                AccountMeta::new_readonly(PUBKEY_EVENT_AUTHORITY, false),
                AccountMeta::new_readonly(PUBKEY_PUMPFUN, false),
            ],
        ))
    }
}

impl Pumpfun {
    pub fn new(endpoint: Arc<TradingEndpoint>) -> Self {
        Self {
            endpoint,
            global_account: OnceCell::new(),
        }
    }

    pub fn get_bonding_curve_pda(mint: &Pubkey) -> anyhow::Result<Pubkey> {
        let seeds: &[&[u8]; 2] = &[BONDING_CURVE_SEED, mint.as_ref()];
        let program_id: &Pubkey = &PUBKEY_PUMPFUN;
        let pda = Pubkey::try_find_program_address(seeds, program_id).ok_or_else(|| anyhow::anyhow!("Failed to find bonding curve PDA"))?;
        Ok(pda.0)
    }

    pub fn get_creator_vault_pda(creator: &Pubkey) -> anyhow::Result<Pubkey> {
        let seeds: &[&[u8]; 2] = &[CREATOR_VAULT_SEED, creator.as_ref()];
        let program_id: &Pubkey = &PUBKEY_PUMPFUN;
        let pda = Pubkey::try_find_program_address(seeds, program_id).ok_or_else(|| anyhow::anyhow!("Failed to find creator vault PDA"))?;
        Ok(pda.0)
    }
}