The SDK is currently in private beta. Reach out if your team wants to integrate.
1

Install Dependencies

heaven-integration-sdk
cargo add heaven-integration-sdk --git https://github.com/heavenxyz/heaven-integration-sdk.git -rev 0c61785e24f92226e1b3a0b288cfa21ade3672ef
solana-sdk
cargo add solana-sdk
solana-client
cargo add solana-client
spl-token
cargo add spl-token --features no-entrypoint
spl-associated-token-account
cargo add spl-associated-token-account --features no-entrypoint
spl-token-2022
cargo add spl-token-2022 --features no-entrypoint
2

Create an RPC Connection

let rpc_client = RpcClient::new("https://api.mainnet-beta.solana.com");
3

Create a Pool

Creating a pool requires several steps, including creating a mint, initializing the transfer fee config, and creating the pool itself. Here’s a simplified version of how to do this:
Currently, only protocol version 2 supports permissionless pool creation.
quickstart.rs
// 1. Set up an RPC client pointing at mainnet-beta.
    let rpc_client =
        solana_client::rpc_client::RpcClient::new("https://api.mainnet-beta.solana.com");

    // 2. Define the fee-payer address (must be funded) and a fresh creator keypair.
    let payer = pubkey!("2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S");
    let creator = Pubkey::new_unique();

    // 3. Specify which version of the protocol config to use, and the program ID.
    // Permissionless pool creation is only supported in version 2 and should be used for development and testing purposes.
    // Until version 1 is updated to support permissionless pool creation, it is recommended to use version 2.
    let protocol_config_version = 2;
    let program_id = heaven::ID;

    // 4. Derive the PDA for the protocol config state, then fetch & parse it.
    let protocol_config_key =
        derive_protocol_config_state(protocol_config_version, &program_id).0;
    let protocol_config_account = rpc_client
        .get_account(&protocol_config_key)
        .expect("Failed to get protocol config account");
    let protocol_config = parse_protocol_config(&protocol_config_account.data)
        .expect("Failed to parse protocol config");

    // ─── Prepare a new SPL-Token 2022 mint for token A ────────────────────────────

    // 5. Generate a new keypair for the mint; this account will hold mint metadata.
    let mint_signer = Keypair::new();
    let token_a_mint = mint_signer.pubkey();
    // Use the SPL-Token 2022 program for extended features (transfer fees).
    let token_a_program = spl_token_2022::ID;
    let token_a_mint_authority = creator; // creator will initially have minting rights

    // 6. Compute how much space the mint needs to include the transfer-fee extension.
    let mint_space =
        ExtensionType::try_calculate_account_len::<spl_token_2022::state::Mint>(&vec![
            ExtensionType::TransferFeeConfig,
        ])
        .unwrap();

    // 7. Query minimum lamports for rent exemption for that account size.
    let rent_exempt = rpc_client
        .get_minimum_balance_for_rent_exemption(mint_space as usize)
        .expect("Failed to get rent exemption");

    // 8. Build the CPI to initialize the transfer-fee config on the mint.
    let initialize_transfer_fee_ix =
        spl_token_2022::extension::transfer_fee::instruction::initialize_transfer_fee_config(
            &spl_token_2022::ID,
            &token_a_mint,
            Some(&protocol_config_key), // fee config authority
            Some(&protocol_config_key), // withdraw authority
            0,                          // transfer fee numerator
            u64::MAX,                   // transfer fee denominator (max precision)
        )
        .unwrap();

    // 9. Build the CPI to initialize the mint itself (decimals from protocol config).
    let initialize_mint_ix = spl_token_2022::instruction::initialize_mint(
        &spl_token_2022::ID,
        &token_a_mint,
        &token_a_mint_authority,
        None,                             // no freeze authority
        protocol_config.token_a_decimals, // decimals as configured
    )
    .unwrap();

    // 10. Build the system instruction to create the mint account on-chain.
    let create_mint_ix = system_instruction::create_account(
        &payer,
        &token_a_mint,
        rent_exempt,
        mint_space as u64,
        &token_a_program,
    );

    // ─── Create and fund the creator’s ATA, then mint tokens ──────────────────────

    // 11. Derive the Associated Token Account (ATA) address for (creator, token A).
    let ata_key =
        get_associated_token_address_with_program_id(&creator, &token_a_mint, &token_a_program);
    // Build the idempotent CPI to create the ATA (no-op if it already exists).
    let create_ata_ix = create_associated_token_account_idempotent(
        &payer,
        &creator,
        &token_a_mint,
        &token_a_program,
    );
    // Mint the initial token A supply into the creator’s ATA.
    let mint_tokens_ix = spl_token_2022::instruction::mint_to(
        &spl_token_2022::ID,
        &token_a_mint,
        &ata_key,
        &token_a_mint_authority,
        &[], // no multisig signers
        protocol_config.initial_token_a_amount,
    )
    .unwrap();

    // 12. Revoke mint authority so no further minting is possible.
    let revoke_mint_authority_ix = spl_token_2022::instruction::set_authority(
        &spl_token_2022::ID,
        &token_a_mint,
        None, // new authority = none (lock forever)
        spl_token_2022::instruction::AuthorityType::MintTokens,
        &token_a_mint_authority,
        &[],
    )
    .unwrap();

    // ─── Quote how much SOL is needed for the pool’s initial purchase ───────────────

    // 13. Convert a human-readable UI amount into raw token amount.
    let initial_purchase_amount =
        ui_amount_to_amount(10_000_000.0, protocol_config.token_a_decimals);
    // 14. Ask the bonding-curve logic how much SOL to spend for that many tokens.
    let max_sol_spend =
        quote_initial_purchase_amount(&protocol_config, initial_purchase_amount)
            .expect("Failed to quote initial purchase amount");

    // ─── Build the Heaven AMM “create pool” instruction ───────────────────────────

    // 15. Construct the CPI to call our on-chain program and create the pool.
    let create_pool_ix = create_initialize_pool_ix(
        &payer,
        &creator,
        initial_purchase_amount,
        max_sol_spend,
        &token_a_program,
        &token_a_mint,
        protocol_config_version,
        &program_id,
    )
    .unwrap();

    // ─── (Optional) Bump compute-unit limits for heavy CPI workloads ────────────────

    let set_compute_unit_limit_ix = ComputeBudgetInstruction::set_compute_unit_limit(200_000);
    let set_compute_unit_price_ix = ComputeBudgetInstruction::set_compute_unit_price(1_000_000);

    // ─── Aggregate all instructions into one transaction ──────────────────────────

    let ixs = vec![
        set_compute_unit_limit_ix,
        set_compute_unit_price_ix,
        create_mint_ix,
        initialize_transfer_fee_ix,
        initialize_mint_ix,
        create_ata_ix,
        mint_tokens_ix,
        revoke_mint_authority_ix,
        create_pool_ix,
    ];

    // ─── Compile, sign, and simulate the transaction ──────────────────────────────

    // 16. For simplicity, use a dummy recent blockhash.
    let recent_blockhash = Hash::default();
    // 17. Build a versioned Message from payer + all instructions.
    let message = Message::try_compile(&payer, &ixs.as_ref(), &[], recent_blockhash)
        .expect("Failed to compile message");
    let versioned_message = VersionedMessage::V0(message);

    // 18. Collect the required signers: payer, creator, and the mint keypair.
    let signers: [&dyn Signer; 3] = [
        &NullSigner::new(&payer),
        &NullSigner::new(&creator),
        &mint_signer,
    ];
    // 19. Create the VersionedTransaction object.
    let versioned_tx = VersionedTransaction::try_new(versioned_message, &signers)
        .expect("Failed to create versioned transaction");

    // 20. Submit the transaction to the RPC and check for errors.
    // ...
4

Fetch Pool State

You can derive the pool address using the token mint addresses and the Heaven program ID.
quickstart.rs
let token_mint = pubkey!("88aUGeGXFNaEyzL48fkzSPWUPhJr3gWrMDD8EH8tCb1");
let program_id = heaven::ID;

let pool_key = derive_standard_liquidity_pool_state(&token_mint, &spl_token::native_mint::ID, &program_id)
    .0;

let pool_account = rpc_client.get_account(&pool_key).unwrap();

let pool_state = parse_liquidity_pool_state(&pool_account.data).unwrap();
5

Fetch Protocol Configuration

You can derive the protocol configuration address using a protocol version number and the Heaven program ID.Currently, only version 1 is supported. In the future, this may change to support multiple protocol versions.Each with a different configuration.
quickstart.rs
let protocol_config_version = 1;

let protocol_config_key = derive_protocol_config_state(protocol_config_version, &program_id).0;

let protocol_config_account = rpc_client.get_account(&protocol_config_key).unwrap();

let protocol_config = parse_protocol_config(&protocol_config_account.data).unwrap();
6

Initialize the Heaven Client

Once you have the pool state, protocol configuration and program ID, you can initialize the HeavenAMM client.
quickstart.rs
let amm = HeavenAmm {
    state: &pool_state,
    program_id: &program_id,
    protocol_config: &protocol_config,
};
7

Quote Buy

You can quote a buy operation using the quote_buy method. This will return the amount of tokens you will receive, the fee, and the fee percentage.
quickstart.rs
let max_sol_spend = (0.01 * 1e9) as u64; // 0.1 SOL in lamports
let ata = get_associated_token_address_with_program_id(
    &user,
    &pool_state.token_a.mint,
    &pool_state.token_a.owner,
);
let current_balance = rpc_client
    .get_token_account_balance(&ata)
    .map(|balance| balance.amount.parse::<u64>().unwrap_or(0))
    .unwrap_or_default();
let (token_output, fee, fee_pct) = amm
    .quote_buy(
        max_sol_spend,
        current_slot,
        current_balance,
        current_sol_usd_price,
    )
    .unwrap();
println!(
    "Buy Quote: Output: {}, Fee: {}, Fee Percentage: {}%",
    amount_to_ui_amount(token_output, pool_state.token_a_decimals()),
    fee,
    fee_pct * 100.0
);
8

Create Buy Instruction

Once you have the quote, you can create the buy instruction.Here max_sol_spend is the maximum amount of SOL you are willing to spend, and minimum_token_output is the minimum amount of tokens you want to receive.
quickstart.rs
let minimum_token_output = token_output;
let buy_ix = amm
    .buy_ix(max_sol_spend, minimum_token_output, &user)
    .unwrap();
9

Quote Sell

You can quote a sell operation using the quote_sell method. This will return the amount of SOL you will receive, the fee, and the fee percentage.
quickstart.rs
let token_amount = token_output;
let (sol_output, fee, fee_pct) = amm
    .quote_sell(token_amount, current_slot, current_sol_usd_price)
    .unwrap();
10

Create Sell Instruction

Once you have the quote, you can create the sell instruction.
quickstart.rs
let sell_ix = amm
    .sell_ix(token_amount, sol_output, &user)
    .unwrap();

Full Example

quickstart.rs
use heaven_integration_sdk::heaven::{
    self,
    amm::{
        HeavenAmm, create_close_wrapped_sol_ix, derive_protocol_config_state,
        derive_standard_liquidity_pool_state, parse_liquidity_pool_state, parse_protocol_config,
        wrap_sol_ixs,
    },
    oracle::{CHAINLINK_SOL_USD_FEED, state::get_latest_sol_usd_price},
};
use solana_client::rpc_config::RpcSimulateTransactionConfig;
use solana_sdk::{
    compute_budget::ComputeBudgetInstruction,
    hash::Hash,
    message::{VersionedMessage, v0::Message},
    pubkey,
    signature::NullSigner,
    transaction::VersionedTransaction,
};
use spl_associated_token_account::{
    get_associated_token_address_with_program_id,
    instruction::create_associated_token_account_idempotent,
};
use spl_token::amount_to_ui_amount;

fn main() {
    let rpc_client =
        solana_client::rpc_client::RpcClient::new("https://api.mainnet-beta.solana.com");

    let account = rpc_client.get_account(&CHAINLINK_SOL_USD_FEED).unwrap();

    let data = account.data;

    let token_mint = pubkey!("88aUGeGXFNaEyzL48fkzSPWUPhJr3gWrMDD8EH8tCb1");
    let protocol_config_version = 1;

    let user = pubkey!("FrXg2ceCwbpSCcW3eLMuzt2xqBp727zqoSC93TkRixvC");

    let program_id = heaven::ID;

    let current_sol_usd_price = get_latest_sol_usd_price(&data).unwrap();

    let pool_key =
        derive_standard_liquidity_pool_state(&token_mint, &spl_token::native_mint::ID, &program_id)
            .0;

    let pool_account = rpc_client.get_account(&pool_key).unwrap();

    let pool_state =
        parse_liquidity_pool_state(&pool_account.data).expect("Failed to parse pool state");

    let protocol_config_key = derive_protocol_config_state(protocol_config_version, &program_id).0;

    let protocol_config_account = rpc_client
        .get_account(&protocol_config_key)
        .expect("Failed to get protocol config account");

    let protocol_config = parse_protocol_config(&protocol_config_account.data)
        .expect("Failed to parse protocol config");

    let amm = HeavenAmm {
        state: &pool_state,
        program_id: &program_id,
        protocol_config: &protocol_config,
    };

    let current_slot = rpc_client.get_slot().unwrap();

    // Calculate buy quote
    let max_sol_spend = (0.01 * 1e9) as u64; // 0.1 SOL in lamports
    let ata = get_associated_token_address_with_program_id(
        &user,
        &pool_state.token_a.mint,
        &pool_state.token_a.owner,
    );
    let current_balance = rpc_client
        .get_token_account_balance(&ata)
        .map(|balance| balance.amount.parse::<u64>().unwrap_or(0))
        .unwrap_or_default();
    let (token_output, fee, fee_pct) = amm
        .quote_buy(
            max_sol_spend,
            current_slot,
            current_balance,
            current_sol_usd_price,
        )
        .expect("Failed to get buy quote");

    println!(
        "Buy Quote: Output: {}, Fee: {}, Fee Percentage: {}%",
        amount_to_ui_amount(token_output, pool_state.token_a_decimals()),
        fee,
        fee_pct * 100.0
    );

    assert!(token_output > 0);

    let (sol_output, fee, fee_pct) = amm
        .quote_sell(token_output, current_slot, current_sol_usd_price)
        .expect("Failed to get sell quote");

    println!(
        "Sell Quote: Output: {}, Fee: {}, Fee Percentage: {}%",
        amount_to_ui_amount(sol_output, pool_state.token_b_decimals()),
        fee,
        fee_pct * 100.0
    );

    // Create buy instruction based on the quote
    let buy_ix = amm
        .buy_ix(max_sol_spend, token_output, &user)
        .expect("Failed to create buy instruction");
    let sell_ix = amm
        .sell_ix(token_output, sol_output, &user)
        .expect("Failed to create sell instruction");

    let set_compute_unit_limit_ix = ComputeBudgetInstruction::set_compute_unit_limit(200_000);
    let set_compute_unit_price_ix = ComputeBudgetInstruction::set_compute_unit_price(1000_000);

    let (mut ixs, ata) = wrap_sol_ixs(max_sol_spend, &user);

    let create_ata_ix = create_associated_token_account_idempotent(
        &user,
        &user,
        &pool_state.token_a.mint,
        &spl_token_2022::ID,
    );

    ixs.insert(0, create_ata_ix);
    ixs.insert(0, set_compute_unit_price_ix);
    ixs.insert(0, set_compute_unit_limit_ix);
    ixs.push(buy_ix);
    ixs.push(sell_ix);

    let close_wrapped_sol_ix = create_close_wrapped_sol_ix(&user, &ata);

    ixs.push(close_wrapped_sol_ix);

    let recent_blockhash = Hash::default();

    let message = Message::try_compile(&user, &ixs.as_ref(), &[], recent_blockhash)
        .expect("Failed to compile message");

    let versioned_message = VersionedMessage::V0(message);

    let versioned_tx = VersionedTransaction::try_new(versioned_message, &[NullSigner::new(&user)])
        .expect("Failed to create versioned transaction");

    let logs = rpc_client.simulate_transaction_with_config(
        &versioned_tx,
        RpcSimulateTransactionConfig {
            replace_recent_blockhash: true,
            ..Default::default()
        },
    );

    assert!(logs.is_ok());
    assert!(
        logs.as_ref().unwrap().value.err.is_none(),
        "Simulation error: {:#?}",
        logs.unwrap().value.err
    );
}
For a more comprehensive example and source code, please refer to the Heaven Integration SDK repository