pump-rust-client 0.1.4

Rust SDK for the pump and pump_amm Solana programs: instruction builders, quoting, PDA helpers, and optional RPC client features.
Documentation
# CPI into pump from your own Anchor program

When your program needs to CPI into pump's `buy_v2` or `sell_v2`, you can
reuse `pump-rust-client` to derive every account meta. The pattern is:

1. Build pump's instruction list with `sdk.buy_v2_instructions(...)`.
2. **Re-target** the trailing trade ix at your wrapper: swap `program_id`
   to your program's id and replace `data` with your wrapper's instruction
   data.
3. Send. The wrapper's account-order matches pump's, so the SDK's metas
   pass through unchanged — including the trailing `program` meta, which
   doubles as the wrapper's CPI target.

The worked example below comes from `devnet-v2-test`, an Anchor program
whose only job is to CPI `buy_v2` / `sell_v2` straight through.

## 1. The wrapper program

The wrapper declares pump via `declare_program!(pump)` (which reads
`idls/pump.json`) and exposes a `buy_v2` instruction whose `Accounts`
struct **mirrors pump's `BuyV2` account order exactly**. From
`devnet-v2-test/programs/devnet-v2-test/src/lib.rs`:

```rust
use anchor_lang::prelude::*;

declare_id!("7enkT8uLQrwKYSRMrazjr1YZPT7unToAtwdLMWa2QLYY");
declare_program!(pump);

#[program]
pub mod test_program {
    use super::*;

    pub fn buy_v2(ctx: Context<BuyV2>, amount: u64, max_sol_cost: u64) -> Result<()> {
        pump::cpi::buy_v2(
            CpiContext::new(
                ctx.accounts.program.to_account_info(),
                pump::cpi::accounts::BuyV2 {
                    global: ctx.accounts.global.to_account_info(),
                    base_mint: ctx.accounts.base_mint.to_account_info(),
                    quote_mint: ctx.accounts.quote_mint.to_account_info(),
                    base_token_program: ctx.accounts.base_token_program.to_account_info(),
                    quote_token_program: ctx.accounts.quote_token_program.to_account_info(),
                    associated_token_program: ctx.accounts.associated_token_program.to_account_info(),
                    fee_recipient: ctx.accounts.fee_recipient.to_account_info(),
                    associated_quote_fee_recipient: ctx.accounts.associated_quote_fee_recipient.to_account_info(),
                    buyback_fee_recipient: ctx.accounts.buyback_fee_recipient.to_account_info(),
                    associated_quote_buyback_fee_recipient: ctx.accounts.associated_quote_buyback_fee_recipient.to_account_info(),
                    bonding_curve: ctx.accounts.bonding_curve.to_account_info(),
                    associated_base_bonding_curve: ctx.accounts.associated_base_bonding_curve.to_account_info(),
                    associated_quote_bonding_curve: ctx.accounts.associated_quote_bonding_curve.to_account_info(),
                    user: ctx.accounts.user.to_account_info(),
                    associated_base_user: ctx.accounts.associated_base_user.to_account_info(),
                    associated_quote_user: ctx.accounts.associated_quote_user.to_account_info(),
                    creator_vault: ctx.accounts.creator_vault.to_account_info(),
                    associated_creator_vault: ctx.accounts.associated_creator_vault.to_account_info(),
                    sharing_config: ctx.accounts.sharing_config.to_account_info(),
                    global_volume_accumulator: ctx.accounts.global_volume_accumulator.to_account_info(),
                    user_volume_accumulator: ctx.accounts.user_volume_accumulator.to_account_info(),
                    associated_user_volume_accumulator: ctx.accounts.associated_user_volume_accumulator.to_account_info(),
                    fee_config: ctx.accounts.fee_config.to_account_info(),
                    fee_program: ctx.accounts.fee_program.to_account_info(),
                    system_program: ctx.accounts.system_program.to_account_info(),
                    event_authority: ctx.accounts.event_authority.to_account_info(),
                    program: ctx.accounts.program.to_account_info(),
                },
            ),
            amount,
            max_sol_cost,
        )?;
        Ok(())
    }
}

#[derive(Accounts)]
pub struct BuyV2<'info> {
    /// CHECK: passed straight to pump
    pub global: UncheckedAccount<'info>,
    /// CHECK: passed straight to pump
    pub base_mint: UncheckedAccount<'info>,
    /// CHECK: passed straight to pump
    pub quote_mint: UncheckedAccount<'info>,
    // ... fields in the same order as pump::cpi::accounts::BuyV2 ...
    #[account(mut)]
    pub user: Signer<'info>,
    // ...
    /// CHECK: pump program; CPI target
    pub program: UncheckedAccount<'info>,
}
```

The full file is at
`devnet-v2-test/programs/devnet-v2-test/src/lib.rs`.

## 2. Client-side: re-target the SDK's instruction

From `devnet-v2-test/programs/devnet-v2-test/tests/buy_sell_v2_cpi.rs`:

```rust
use anchor_lang::InstructionData;
use solana_sdk::instruction::Instruction;
use solana_sdk::pubkey::Pubkey;

use pump_rust_client::{constants, PumpSdk};

/// Re-target the trailing pump ix produced by `pump-rust-client` so it hits
/// the wrapper. The account-meta order is identical between pump's `BuyV2`
/// and the wrapper, and the trailing `program` meta already points at pump
/// (it doubles as the CPI target), so we only swap program id and data.
fn retarget_to_wrapper(ix: &mut Instruction, new_program_id: Pubkey, new_data: Vec<u8>) {
    ix.program_id = new_program_id;
    ix.data = new_data;
}

let sdk = PumpSdk::new();
let mut pump_buy_ixs = sdk
    .buy_v2_instructions(
        &global,
        &bonding_curve,
        mint,
        constants::SPL_TOKEN_PROGRAM_ID,
        user.pubkey(),
        buy_amount,
        max_sol_cost,
    )
    .expect("buy_v2_instructions");

let buy_args = devnet_v2_test::instruction::BuyV2 {
    amount: buy_amount,
    max_sol_cost,
};
let last = pump_buy_ixs.last_mut().expect("trailing buy_v2 ix");
retarget_to_wrapper(last, devnet_v2_test::ID, buy_args.data());

// `pump_buy_ixs` now starts with idempotent ATA creates and ends with the
// re-targeted wrapper call. Send as-is.
```

`sell_v2` follows the identical pattern with
`sdk.sell_v2_instructions(...)` and `devnet_v2_test::instruction::SellV2`.

## 3. Why this works

- The SDK builds the trade ix with **pump's account layout** (see
  `V2TradeAccounts::derive` in `src/sdk/pump_v2.rs`).
- The wrapper's `Accounts` struct lists the same accounts in the same
  order, so Anchor accepts the meta list verbatim.
- The trailing meta is the pump program id — the wrapper picks it up via
  `ctx.accounts.program` and uses it as the CPI target. No meta surgery is
  needed beyond the program-id and data swap.

## Caveats

- **Account order matters.** If your wrapper reorders, renames, or adds
  accounts, the drop-in re-target breaks. In that case build the meta list
  manually using `pda::pump::*` helpers (see `src/pda.rs`) and the public
  `PumpSdk` PDAs.
- **IDL.** Your wrapper crate must ship the pump IDL alongside it (e.g.
  `devnet-v2-test/programs/devnet-v2-test/idls/pump.json`) so
  `declare_program!(pump)` can resolve `pump::cpi::accounts::BuyV2`.
- **Testing.** Build and deploy the wrapper to devnet (or any cluster
  where the pump program is live), then point your client RPC at that
  cluster and exercise it the same way as the SDK examples.