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-sdkOptional features:
serde:SerializeandDeserializeon accounts, events and types.anchor:anchor-langinterop for on-chain callers.
§Rules
- For a trade, read
platform_configfrom the bonding curve or the pool. - A partner other than
DEFAULT_PARTNERmust sign. constants::find_platform_address(slug)returns the key of a platform.- Each trade, migrate and create builder takes a
MintInfofor the base mint and aMintInfofor the quote mint. For the new base mint of a create, useMintInfo::launch. Load aMintInfoas below. Refresh it on a timer, like any account: load it and fill it again. For WSOL and USDC, you can also useconstants::WSOL_MINT_INFOandconstants::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
generatedmodules. 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: "e.owner, data: "e.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
§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