A Rust library that works great with LiteSVM for testing your Solana programs. High-level abstractions for common Solana operations: wallets, transactions, tokens, Token Extensions, transfer hooks, PDAs, and program deployment.
- 🚀 Program Deployment: Deploy programs from files or bytes (
include_bytes!) - 💸 Transaction Utilities: Send v1 transactions (SIMD-0385) from instructions with proper signing
- 🪙 Token Operations: Create mints, ATAs, mint tokens, check balances
- 🔐 Token Extensions: Create mints with transfer hooks, transfer fees, permanent delegates, non-transferable tokens, and more
- 🪝 Transfer Hook Support: ExtraAccountMetaList setup and transfer helpers
- 👛 Wallet Management: Create funded wallets in one call
- 🔑 PDA Utilities: Type-safe seed handling with the
seeds!macro - 📚 Well Documented: Extensive docs and examples
cargo add --dev solana-kite
or add to your Cargo.toml:
[dev-dependencies]
solana-kite = "0.6"Compatibility: Solana 3.x / Anchor 1.2 / LiteSVM 0.16
use solana_kite::{create_wallet, create_token_mint, create_associated_token_account, mint_tokens_to_token_account};
use litesvm::LiteSVM;
use solana_signer::Signer;
let mut svm = LiteSVM::new();
// Create a funded wallet
let authority = create_wallet(&mut svm, 1_000_000_000).unwrap();
// Create a token mint (6 decimals, like USDC). The last argument of every function
// that sends a transaction is its compute unit limit: None simulates the
// transaction to find it.
let mint = create_token_mint(&mut svm, &authority, 6, None, None).unwrap();
// Create associated token account
let ata = create_associated_token_account(&mut svm, &authority.pubkey(), &mint, &authority, None).unwrap();
// Mint 1000 tokens
mint_tokens_to_token_account(&mut svm, &mint, &ata, 1_000_000_000, &authority, None).unwrap();use solana_kite::{create_wallet, create_wallets};
let wallet = create_wallet(&mut svm, 1_000_000_000)?; // 1 SOL
let wallets = create_wallets(&mut svm, 5, 1_000_000_000)?; // 5 wallets, 1 SOL eachuse solana_kite::{get_sol_balance, assert_sol_balance};
let balance = get_sol_balance(&svm, &wallet.pubkey()); // returns lamports, 0 if account missing
assert_sol_balance(&svm, &wallet.pubkey(), 1_000_000_000, "Should have 1 SOL");use solana_kite::send_transaction_from_instructions;
send_transaction_from_instructions(
&mut svm,
vec![instruction1, instruction2],
&[&payer, &other_signer],
&payer.pubkey(),
Some(200_000), // compute unit limit, or None to simulate and use what the simulation used
)?;Every transaction kite builds is a v1 transaction (SIMD-0385, live on mainnet beta since 15 September
2026), including the ones its token, Token Extensions and transfer hook helpers send. create_wallet
funds a wallet with LiteSVM's airdrop, which LiteSVM builds as a legacy transaction.
A v1 transaction holds up to 4,096 bytes. Its compute unit, loaded account data and heap limits, and
its priority fee, are part of the transaction itself, in a TransactionConfig in the message, and an
unset compute unit or loaded account data limit is zero. So:
- The compute unit limit is the last argument of
send_transaction_from_instructionsand of every token, Token Extensions and transfer hook helper:compute_unit_limit: Option<u32>.Some(limit)sends the transaction with that limit. Choose a number your instructions run in, and the test fails if they need more: a transaction that runs out of compute units fails.Nonesimulates the transaction in LiteSVM and sends it with the compute units the simulation used. It never fails for running out, so it does not check how many compute units your program uses.
- Kite sets the loaded account data limit to the most a transaction may load, Agave's
MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES(64 MiB insolana-compute-budget4.2). The heap is 32 KiB and there is no priority fee. Seeget_transaction_config. - ComputeBudget instructions are rejected. A ComputeBudget instruction in a v1 transaction does nothing, so kite returns an error for one rather than let a test believe it set a limit or priority fee.
To read logs or compute units, measure a transaction, or set every limit yourself, build it with
build_transaction_from_instructions and send it through LiteSVM:
use solana_kite::{build_transaction_from_instructions, get_transaction_config};
let transaction = build_transaction_from_instructions(
&svm,
&instructions,
&[&payer],
&payer.pubkey(),
get_transaction_config(50_000),
)?;
let metadata = svm.send_transaction(transaction).unwrap();
println!("{} compute units", metadata.compute_units_consumed);use solana_kite::{
create_token_mint, create_associated_token_account, get_token_account_address,
mint_tokens_to_token_account, get_token_account_balance, assert_token_account_balance,
};
let mint = create_token_mint(&mut svm, &authority, 9, None, None)?;
// Pre-compute the ATA address before creating it
let ata_address = get_token_account_address(&owner.pubkey(), &mint);
let ata = create_associated_token_account(&mut svm, &owner.pubkey(), &mint, &payer, None)?;
assert_eq!(ata_address, ata);
mint_tokens_to_token_account(&mut svm, &mint, &ata, 1_000_000_000, &authority, None)?;
assert_token_account_balance(&svm, &ata, 1_000_000_000, "Should have 1B tokens");Create mints with extensions: transfer hooks, transfer fees, permanent delegates, non-transferable tokens, and more:
use solana_kite::{
create_token_extensions_mint, create_token_extensions_account,
get_token_extensions_account_address, mint_tokens_to_token_extensions_account,
transfer_checked_token_extensions, MintExtension, TokenAccountState,
};
// Create a mint with transfer fee extension
let mint = create_token_extensions_mint(
&mut svm,
&authority,
6,
None,
&[MintExtension::TransferFee {
fee_basis_points: 100, // 1%
maximum_fee: 1_000_000,
}],
None,
)?;
// Pre-compute an ATA address before creating it (e.g. to pass to a program instruction)
let sender_ata_address = get_token_extensions_account_address(&sender.pubkey(), &mint);
// Create token accounts, mint, and transfer
let sender_ata = create_token_extensions_account(&mut svm, &sender.pubkey(), &mint, &payer, None)?;
let receiver_ata = create_token_extensions_account(&mut svm, &receiver.pubkey(), &mint, &payer, None)?;
mint_tokens_to_token_extensions_account(&mut svm, &mint, &sender_ata, 1_000_000, &authority, None)?;
transfer_checked_token_extensions(&mut svm, &sender_ata, &mint, &receiver_ata, &sender, 500_000, 6, &[], None)?;Supported extensions:
TransferHook- attach a hook program to transfersTransferFee- automatic fee collection on transfersMintCloseAuthority- allow closing the mint accountPermanentDelegate- irrevocable delegate authorityNonTransferable- soulbound tokensDefaultAccountState- new token accounts start in a givenTokenAccountState(Frozen, Initialized, or Uninitialized)InterestBearing- display interest rate on tokenMetadataPointer- point to onchain metadata
use solana_kite::{
build_hook_accounts, get_hook_accounts_address, initialize_hook_accounts, HookAccount,
};
// The extra accounts your hook program reads on every transfer
let hook_accounts: Vec<HookAccount> = vec![/* ... */];
// Initialize the ExtraAccountMetaList account for your hook program
initialize_hook_accounts(
&mut svm,
&hook_program_id,
&mint,
&authority,
&hook_accounts,
None,
)?;
// The ExtraAccountMetaList PDA, e.g. to pass to your hook program's instructions
let extra_account_meta_list_address = get_hook_accounts_address(&mint, &hook_program_id);
// Build the account list to pass as `hook_accounts` to transfer_checked_token_extensions
let transfer_hook_account_metas = build_hook_accounts(&mint, &hook_program_id, &hook_accounts);use solana_kite::{deploy_program, deploy_program_bytes};
// From a file path
deploy_program(&mut svm, &program_id, "./target/deploy/my_program.so")?;
// From bytes (works with include_bytes!)
let bytes = include_bytes!("../target/deploy/my_program.so");
deploy_program_bytes(&mut svm, &program_id, bytes)?;use solana_kite::{get_pda_and_bump, Seed};
let (pda, bump) = get_pda_and_bump(
&seeds!["user-account", user_address, 42u64],
&program_id,
);use solana_kite::check_account_is_closed;
check_account_is_closed(&svm, &account_pubkey, "Account should be closed after this instruction");Every function that can fail returns Result<T, SolanaKiteError>:
use solana_kite::SolanaKiteError;
match some_operation() {
Ok(result) => println!("Success: {:?}", result),
Err(SolanaKiteError::TransactionFailed(message)) => eprintln!("Transaction failed: {message}"),
Err(SolanaKiteError::TokenOperationFailed(message)) => eprintln!("Token operation failed: {message}"),
Err(error) => eprintln!("Error: {error}"),
}cargo run --example basic_usage
cargo run --example token_operations
cargo run --example token_extensions_operations
cargo run --example transfer_hook_operationscargo testMIT - see LICENSE.md.
Made with ❤️ for the Solana ecosystem