Skip to main content

Crate sendfun_sdk

Crate sendfun_sdk 

Source
Expand description

§sendfun-sdk

Rust SDK for the send.fun programs on Solana.

  • launchpad: token creation, bonding curve trades, migration.
  • dex: AMM trades after migration.
  • nexus: staking and partner fees.
  • transfer_hook: Token-2022 transfer-hook resolution.
  • utils: MintInfo.

The SDK builds instructions, decodes accounts and events, and calculates quotes (launchpad::quote, dex::quote). It has no RPC client and does not send transactions.

Fetch accounts with your own client. Compatible: solana-rpc-client 3.x and 4.1+, blocking and nonblocking. Not compatible: 4.0.x.

Decode an account with the from_account function of its type. from_account checks the owner, then the discriminator:

ⓘ
use sendfun_sdk::launchpad::accounts::BondingCurve;

let account = rpc.get_account(&address)?;
let curve = BondingCurve::from_account(&account.owner, &account.data)?;

get_account fails when the account does not exist. For an account that can be missing, use get_account_with_commitment. Its value is None for a missing account. Decode with from_account_maybe. It returns Ok(None) for an uncreated PDA that holds lamports.

§Install

cargo add sendfun-sdk

Optional features:

  • serde: Serialize and Deserialize on accounts, events and types.
  • anchor: anchor-lang interop for on-chain callers.

§Rules

  • For a trade, read platform_config from the bonding curve or the pool.
  • A partner other than DEFAULT_PARTNER must sign.
  • constants::find_platform_address(slug) returns the key of a platform.
  • Each trade, migrate and create builder takes a MintInfo for the base mint and a MintInfo for the quote mint. For the new base mint of a create, use MintInfo::launch. Load a MintInfo as below. Refresh it on a timer, like any account: load it and fill it again. For WSOL and USDC, you can also use constants::WSOL_MINT_INFO and constants::USDC_MINT_INFO.
  • The trade and migrate builders refuse a base mint that is not a Token-2022 mint.
  • A build can fail with HookError::NotLoaded. See its doc.
  • The SDK refuses a hook list that it cannot resolve before the instruction runs. A transaction can still fail at the hook. Simulate it.
  • Do not edit the generated modules. They come from the program IDLs.

Read the mints and the Clock in one call, then fill each hook:

ⓘ
use sendfun_sdk::transfer_hook::{AccountView, FetchedAccount};
use sendfun_sdk::utils::MintInfo;
use sendfun_sdk::constants::CLOCK_SYSVAR_ID as CLOCK;

// One call, so the fee is the fee at the mint read.
let accounts = rpc.get_multiple_accounts(&[base_mint, quote_mint, CLOCK])?;
let [Some(base), Some(quote), Some(clock)] = accounts.as_slice() else {
    return Err("missing account".into());
};
let clock = AccountView { owner: &clock.owner, data: &clock.data };
let mut base_mint_info = MintInfo::load(
    base_mint,
    AccountView { owner: &base.owner, data: &base.data },
    clock,
)?;
let mut quote_mint_info = MintInfo::load(
    quote_mint,
    AccountView { owner: &quote.owner, data: &quote.data },
    clock,
)?;

for info in [&mut base_mint_info, &mut quote_mint_info] {
    loop {
        let next = info.missing()?;
        if next.is_empty() {
            break;
        }
        for (address, account) in next.iter().zip(rpc.get_multiple_accounts(&next)?) {
            info.insert(*address, account.map(|a| FetchedAccount { owner: a.owner, data: a.data }));
        }
    }
}

A builder quotes the fee at the epoch of the read. Read the mint again when the epoch changes. transfer_fee holds the whole FeeSchedule: fee_at(epoch) gives the fee at another epoch, and newer_epoch the epoch the fee changes. MintInfo::load_at reads a mint at an epoch, with no Clock. hook_program and validation_address() give a hooked mint’s hook program and the account that holds its list.

A router that cannot simulate checks each trade direction with transfer_hook::route::trade. It takes both MintInfos, and reads every other account a list reads from the router’s own cache. transfer_hook::route::screen checks a direction for any trader, before the router knows the trader. transfer_hook::route::MarketLoad loads and screens a market at each update. It names the accounts to fetch and the accounts to declare for the next update.

§Documentation

https://docs.send.fun

§License

MIT, see the LICENSE file.

§Reading accounts

Read an account that can be missing:

use sendfun_sdk::nexus::accounts::PartnerConfig;

let partner = match rpc
    .get_account_with_commitment(&pda, rpc.commitment())?
    .value
{
    Some(account) => {
        PartnerConfig::from_account_maybe(&account.owner, &account.data)?
    }
    None => None,
};

Read several accounts in one call. The RPC accepts at most 100 addresses per call:

use sendfun_sdk::launchpad::accounts::BondingCurve;

let curves = rpc
    .get_multiple_accounts(&keys)?
    .iter()
    .map(|account| match account {
        Some(account) => {
            BondingCurve::from_account_maybe(&account.owner, &account.data)
        }
        None => Ok(None),
    })
    .collect::<Result<Vec<_>, _>>()?;

Re-exports§

pub use spl_token_2022_interface;

Modules§

constants
dex
launchpad
math
nexus
transfer_fee
Reads the transfer fee and the pause of a Token-2022 mint from raw account data.
transfer_hook
Resolves the Token-2022 transfer-hook accounts of an instruction from accounts that you fetch.
utils