> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rugfreepump.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Buy

> Integrate with the protocol's buy instruction to purchase tokens from an active bonding-curve sale.

The `buy` instruction purchases tokens from an active RugFreePump bonding-curve sale using SOL.

The instruction accepts a gross SOL amount, deducts the configured protocol fee, denominates the remaining SOL contribution in USD-equivalent terms using a validated Pyth SOL/USD price feed, calculates the corresponding token amount using the bonding curve pricing model, and adds the purchased token amount to the buyer's cumulative token allocation recorded in their on-chain account.

Purchased tokens are not transferred directly to the buyer during the purchase. They become claimable only after successful liquidity migration.

## Instruction Execution Requirements

### Sale Status

Before constructing a `buy` transaction, read the following fields from the required accounts and verify that the sale is eligible for purchase:

| Field | Account | Required value / condition |
| :- | :- | :- |
| `is_platform_maintenance_mode_enabled` | `admin_states_pda` | `false` |
| `is_sale_active` | `bonding_curve_pda` | `true` |
| `is_sale_finalized` | `bonding_curve_pda` | `false` |
| `is_finalized_sale_canceled` | `bonding_curve_pda` | `false` |
| `sale_expires_at` | `bonding_curve_pda` | Current Unix timestamp must be **less than** this value |

If the current Unix timestamp is **greater than or equal to** `sale_expires_at`, the program sets `is_sale_active` to `false` and rejects the purchase.

Because these values can change between transaction construction and execution, client-side checks should be treated as **pre-validation only**. The on-chain program performs the authoritative checks when the transaction executes.

### Sale Cap and Finalization Buffer

The bonding-curve sale uses **fixed-point USD-equivalent values with 2 decimal places** for its sale cap and finalization threshold. Buyers contribute **SOL**, and the value of the SOL contribution after the buy fee is deducted is measured against these limits using the validated SOL/USD price.

| Constant | Raw Value | Human-Readable | Purpose |
| :- | :- | :- | :- |
| `MAX_USD_RAISE_CAP_IN_USD_E2` | `1,615,000` | 16,150.00 USD | Maximum USD-equivalent value that can be raised through SOL contributions. |
| `SALE_FINALIZATION_BUFFER_USD_E2` | `5,000` | 50.00 USD | Remaining USD-equivalent sale capacity at which the sale is finalized. |

* **Contribution Valuation:** The buy fee is deducted from the gross SOL amount first. The remaining SOL amount is then valued in USD-equivalent terms using the validated SOL/USD price.
* **Capacity Constraint:** The USD-equivalent value of the SOL amount remaining after the buy fee must not exceed the sale's remaining USD-equivalent capacity.
* **Auto-Finalization:** After a successful purchase, the sale is finalized when the remaining USD-equivalent capacity falls below `SALE_FINALIZATION_BUFFER_USD_E2`.

The program determines the sale's remaining USD-equivalent capacity by subtracting `bonding_curve_pda.total_raised_usd_e2` from `MAX_USD_RAISE_CAP_IN_USD_E2`.

### Minimum and Maximum Purchase Limits

* **Minimum Purchase:** `MIN_BUY_AMOUNT_IN_USD_E2 = 100`, representing 1.00 USD. After the buy fee is deducted, the remaining SOL amount must have a USD-equivalent value of at least 1.00 USD.
* **Maximum Purchase:** There is no separate hard-coded per-purchase maximum. The maximum permitted SOL amount is constrained by the sale's **remaining USD-equivalent capacity**, after accounting for the buy fee and the current SOL/USD price.
* **Dynamic SOL Limits:** The maximum valid SOL purchase amount changes with the sale's remaining USD-equivalent capacity, the configured buy fee, and the current SOL/USD price.

### Buy Fee

The `lamports_amount` argument represents the **gross SOL purchase amount**.

The configured buy fee is deducted from the gross SOL amount before the remaining SOL amount is valued using the SOL/USD price and checked against the minimum purchase and remaining sale capacity.

The current initialized buy fee is **100 bps (1%)** and is read from `global_states_pda.buy_fee_bps`.

The configured fee may change through protocol administration.

### Buyer SOL Balance

The buyer must have sufficient SOL to cover the submitted gross `lamports_amount`.

If `buyer_accumulation_pda` does not already exist, the buyer also pays the required account creation rent.

The transaction must therefore provide enough SOL for both the gross purchase amount and any required account initialization.

### Slippage Protection

The `buy` instruction includes slippage protection based on the bonding-curve state supplied by the caller.

Before constructing the transaction, read the following fields from `bonding_curve_pda`:

* `total_raised_usd_e2`
* `total_sold_tokens_e6`

Pass these current values as:

* `expected_total_raised_usd_e2`
* `expected_total_sold_tokens_e6`

The program compares the expected average bonding-curve price derived from these values with the current average price at execution.

`slippage_bps` specifies the maximum permitted increase in the average price.

If the execution-time increase exceeds `slippage_bps`, the transaction is rejected.

Because the bonding-curve state can change before execution, these values should be fetched immediately before constructing the transaction.

### SOL/USD Price Feed

The `sol_usd_price_feed` account must contain a valid Pyth `PriceUpdateV2` account for the SOL/USD price feed.

Before the price is used for the purchase calculation, the program validates:

* The Pyth update has `VerificationLevel::Full`.
* The update corresponds to the **Pyth SOL/USD feed ID** configured in the RugFreePump program.
* The price update is no older than **60 seconds**.
* The reported price is **positive**.
* The oracle confidence interval does not exceed **2% of the reported price**.

The validated price is scaled to a **fixed-point value with 2 decimal places** before being used in the purchase calculation.

For example: **123.45 USD → 12,345**

If any of these validations fail, the `buy` transaction is rejected.

#### Pyth SOL/USD Feed ID

```text theme={null}
0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d
```

#### Current `PriceUpdateV2` Account

```text theme={null}
7UVimffxr9ow1uXYxsr4LHAcV58mLzhmwaeKvJ1pjLiE
```

This is the account currently used by RugFreePump, but it is not a fixed account required by the program. Developers may supply another valid Pyth `PriceUpdateV2` account containing an acceptable update for the same SOL/USD feed, provided it satisfies the program's validation requirements.

## Rust

### Instruction Anchor Signature Definition

```rust theme={null}
pub fn buy(
    ctx: Context<Buy>,
    lamports_amount: u64,
    expected_total_raised_usd_e2: u64,
    expected_total_sold_tokens_e6: u64,
    slippage_bps: u16,
) -> Result<()>
```

### Instruction Anchor Accounts Definition

```rust theme={null}
#[derive(Accounts)]
#[instruction(
    lamports_amount: u64,
    expected_total_raised_usd_e2: u64,
    expected_total_sold_tokens_e6: u64,
    slippage_bps: u16
)]
pub struct Buy<'info> {
    /// The user sending SOL
    #[account(mut)]
    pub buyer: Signer<'info>,

    /// The token mint being sold
    #[account(
        constraint = mint.key() == bonding_curve_pda.token_mint_address
    )]
    pub mint: Account<'info, Mint>,

    #[account(
        seeds = [b"admin-states"],
        bump
    )]
    pub admin_states_pda: Box<Account<'info, AdminStatesPDA>>,

    /// Global states PDA (tracks global sale settings)
    #[account(
        seeds = [b"global-states"],
        bump,
    )]
    pub global_states_pda: Account<'info, GlobalStatesPDA>,

    /// Bonding curve PDA (tracks state for this token sale)
    #[account(
        mut,
        seeds = [b"bonding-curve", mint.key().as_ref()],
        bump,
    )]
    pub bonding_curve_pda: Account<'info, BondingCurvePDA>,

    /// Bonding curve vault (SOL vault for this bonding curve)
    #[account(
        mut,
        seeds = [b"bonding-curve-vault", mint.key().as_ref()],
        bump,
    )]
    pub bonding_curve_vault: SystemAccount<'info>,

    /// Buyer accumulation PDA (tracks how much this buyer has purchased)
    #[account(
        init_if_needed,
        payer = buyer,
        space = BuyerAccumulationPDA::LEN,
        seeds = [
            b"buyer-accumulation",
            bonding_curve_pda.key().as_ref(),
            buyer.key().as_ref(),
        ],
        bump,
    )]
    pub buyer_accumulation_pda: Account<'info, BuyerAccumulationPDA>,

    /// Platform fee vault (validated against seeds)
    #[account(
        mut,
        seeds = [b"platform-fee-vault"],
        bump,
    )]
    pub platform_fee_vault: SystemAccount<'info>,

    // Pyth SOL/USD price feed account
    pub sol_usd_price_feed: Account<'info, PriceUpdateV2>,

    /// Programs
    pub system_program: Program<'info, System>,
}
```

## Instruction Data

| Argument | Type | Required | Required format | Description |
| - | - | - | - | - |
| `lamports_amount` | `u64` | Yes | Unsigned 64-bit integer representing the SOL amount in lamports (`1 SOL = 1,000,000,000 lamports`) | Gross SOL amount submitted by the buyer before the buy fee. The resulting net USD-equivalent contribution must satisfy the configured minimum purchase amount and must not exceed the remaining sale cap |
| `expected_total_raised_usd_e2` | `u64` | Yes | Unsigned integer representing a USD value with 2 decimal places (`1.00 USD = 100 units`, `123.45 USD = 12,345 units`) | The expected `total_raised_usd_e2` value fetched off-chain from the bonding-curve PDA account during transaction construction. It is used as a variable to calculate the expected average bonding-curve price for slippage protection |
| `expected_total_sold_tokens_e6` | `u64` | Yes | Unsigned integer representing a token amount with 6 decimal places (`1 token = 1,000,000 token units`) | The expected `total_sold_tokens_e6` value fetched off-chain from the bonding-curve PDA account during transaction construction. It is used as a variable to calculate the expected average bonding-curve price for slippage protection |
| `slippage_bps` | `u16` | Yes | Unsigned integer in basis points (`1% = 100 bps`, `100% = 10,000 bps`) | The maximum permitted increase in the bonding curve's average price, measured between the expected average price calculated from the off-chain supplied values and the current average price calculated by the `buy` instruction during execution |

The `expected_total_raised_usd_e2` and `expected_total_sold_tokens_e6` values are state snapshots used by the `buy` instruction's slippage check logic. Read the current **`bonding_curve_pda`** account, derived with seeds `[b"bonding-curve", mint]`, immediately before constructing the transaction.

These values, fetched from the bonding-curve PDA account, are already stored in their required formats and can be passed directly without unit conversion.

## Instruction Accounts

The following accounts must be supplied to `buy` in the order defined by the instruction.

| Account | Address / derivation | Required state / role |
| - | - | - |
| `buyer` | Buyer wallet address | Must sign the transaction and pay rent for required account initialization |
| `mint` | Address of the token being purchased | Must exist and match `bonding_curve_pda.token_mint_address` |
| `admin_states_pda` | RugFreePump PDA: seeds `[b"admin-states"]` | Must exist |
| `global_states_pda` | RugFreePump PDA: seeds `[b"global-states"]` | Must exist |
| `bonding_curve_pda` | RugFreePump PDA: seeds `[b"bonding-curve", mint]` | Must exist as the data account that stores and controls the bonding-curve sale state for `mint` |
| `bonding_curve_vault` | RugFreePump PDA: seeds `[b"bonding-curve-vault", mint]` | Must exist and hold the bonding-curve sale's SOL |
| `buyer_accumulation_pda` | RugFreePump PDA: seeds `[b"buyer-accumulation", bonding_curve_pda, buyer]` | Created with `init_if_needed` when missing; `buyer` pays the required rent |
| `platform_fee_vault` | RugFreePump PDA: seeds `[b"platform-fee-vault"]` | Must exist and receive the buy fee |
| `sol_usd_price_feed` | RugFreePump uses: `7UVimffxr9ow1uXYxsr4LHAcV58mLzhmwaeKvJ1pjLiE` | Must be a valid Pyth PriceUpdateV2 account for the required SOL/USD feed and satisfy the program's validation requirements. The program does not require this specific account |
| `system_program` | System Program: `11111111111111111111111111111111` | Required Solana program account |

## TypeScript

### Transaction Construction

The following example derives the required accounts, reads the current `bonding_curve_pda` data account state for slippage protection, constructs the `buy` instruction, and submits it in a versioned Solana transaction.

```ts theme={null}
import { Program } from "@coral-xyz/anchor";
import {
  PublicKey,
  SystemProgram,
  TransactionMessage,
  VersionedTransaction,
} from "@solana/web3.js";
import BN from "bn.js";

// Your configured Anchor program, connection, and wallet
const program = new Program(IDL, provider);

const PROGRAM_ID = new PublicKey("RUGFREEPUMP_PROGRAM_ID");
const mint = new PublicKey("TOKEN_MINT_ADDRESS");
const buyer = wallet.publicKey;

const lamportsAmount = new BN("1000000000"); // 1 SOL
const slippageBps = 200; // 2%

// Derive required PDAs
const [adminStatesPda] = PublicKey.findProgramAddressSync(
  [Buffer.from("admin-states")],
  PROGRAM_ID,
);

const [globalStatesPda] = PublicKey.findProgramAddressSync(
  [Buffer.from("global-states")],
  PROGRAM_ID,
);

const [bondingCurvePda] = PublicKey.findProgramAddressSync(
  [
    Buffer.from("bonding-curve"),
    mint.toBuffer(),
  ],
  PROGRAM_ID,
);

const [bondingCurveVault] = PublicKey.findProgramAddressSync(
  [
    Buffer.from("bonding-curve-vault"),
    mint.toBuffer(),
  ],
  PROGRAM_ID,
);

const [buyerAccumulationPda] = PublicKey.findProgramAddressSync(
  [
    Buffer.from("buyer-accumulation"),
    bondingCurvePda.toBuffer(),
    buyer.toBuffer(),
  ],
  PROGRAM_ID,
);

const [platformFeeVault] = PublicKey.findProgramAddressSync(
  [Buffer.from("platform-fee-vault")],
  PROGRAM_ID,
);

// Fetch the current bonding-curve state
const bondingCurveAccount = await program.account.bondingCurvePda.fetch(bondingCurvePda);

// Read the state snapshot required for slippage protection
const expectedTotalRaisedUsdE2 = new BN(
  bondingCurveAccount.totalRaisedUsdE2.toString(),
);

const expectedTotalSoldTokensE6 = new BN(
  bondingCurveAccount.totalSoldTokensE6.toString(),
);

// Construct the buy instruction
const buyInstruction = await program.methods
  .buy(
    lamportsAmount,
    expectedTotalRaisedUsdE2,
    expectedTotalSoldTokensE6,
    slippageBps,
  )
  .accounts({
    buyer,
    mint,
    adminStatesPda,
    globalStatesPda,
    bondingCurvePda,
    bondingCurveVault,
    buyerAccumulationPda,
    platformFeeVault,
    solUsdPriceFeed: SOL_USD_PRICE_FEED_ACCOUNT,
    systemProgram: SystemProgram.programId,
  })
  .instruction();

// Fetch a recent blockhash
const { blockhash } = await provider.connection.getLatestBlockhash("confirmed");

// Build the versioned transaction
const messageV0 = new TransactionMessage({
  payerKey: buyer,
  recentBlockhash: blockhash,
  instructions: [buyInstruction],
}).compileToV0Message();

const transaction = new VersionedTransaction(messageV0);

// Sign and submit
transaction.sign([wallet.payer]);

const signature = await provider.connection.sendTransaction(transaction);

console.log(signature);
```

`RUGFREEPUMP_PROGRAM_ID`, `IDL`, `provider`, `wallet`, `mint`, and `SOL_USD_PRICE_FEED_ACCOUNT` are application-specific values supplied by the integrating application.

The `buyer_accumulation_pda` is created automatically by the `buy` instruction if it does not already exist. The buyer pays the required account rent.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.