sendfun-sdk 4.0.0

Rust SDK for send.fun programs
Documentation
use crate::constants::{
	ATA_PROGRAM_ID, DEX_PROGRAM_ID, LAUNCHPAD_PROGRAM_ID, NEXUS_PROGRAM_ID,
	SYSTEM_PROGRAM_ID, TOKEN_2022_PROGRAM_ID,
};
use crate::transfer_hook::{HookLeg, HookLegs};
use crate::utils::{
	BuildError, MintInfo, TradeMints, append_hooks, check_base_mint,
};
use solana_address::Address;
use solana_instruction::Instruction;

#[derive(Clone, Debug)]
pub struct BuildMigrateParams<'a> {
	pub caller: &'a Address,
	/// From [`MintInfo::load`].
	pub base_mint_info: &'a MintInfo,
	/// From [`MintInfo::load`].
	pub quote_mint_info: &'a MintInfo,
}

/// Builds `migrate`. Simulate the transaction: a hook can refuse it.
pub fn build_migrate_instruction(
	p: &BuildMigrateParams<'_>,
) -> Result<Instruction, BuildError> {
	check_base_mint(p.base_mint_info)?;
	let (instruction, legs) = bare_migrate_instruction(p);
	append_hooks(
		instruction,
		TradeMints {
			base: p.base_mint_info,
			quote: p.quote_mint_info,
		},
		&legs,
	)
}

/// The instruction and its two legs, quote first, as the program moves them.
/// Each leg moves a curve vault into the pool vault that `migrate` opens,
/// signed by the curve. The program writes no account that a list can read
/// before the transfers.
fn bare_migrate_instruction(
	p: &BuildMigrateParams<'_>,
) -> (Instruction, HookLegs) {
	let base_mint = &p.base_mint_info.address;
	let quote_mint = &p.quote_mint_info.address;
	let quote_token_program = &p.quote_mint_info.token_program;

	let (bonding_curve, _) =
		super::pda::find_bonding_curve_pda(base_mint, quote_mint);
	let (base_vault, _) = crate::utils::find_associated_token_pda(
		&bonding_curve,
		base_mint,
		&TOKEN_2022_PROGRAM_ID,
	);
	let (quote_vault, _) = crate::utils::find_associated_token_pda(
		&bonding_curve,
		quote_mint,
		quote_token_program,
	);
	let (creator_fee_config, _) =
		super::pda::find_creator_fee_config_pda(base_mint, quote_mint);

	let (pool, _) = crate::dex::pda::find_pool_pda(base_mint, quote_mint);
	let (lp_mint, _) = crate::dex::pda::find_lp_mint_pda(base_mint, quote_mint);
	let (pool_base_vault, _) = crate::utils::find_associated_token_pda(
		&pool,
		base_mint,
		&TOKEN_2022_PROGRAM_ID,
	);
	let (pool_quote_vault, _) = crate::utils::find_associated_token_pda(
		&pool,
		quote_mint,
		quote_token_program,
	);
	let (pool_lp_account, _) = crate::utils::find_associated_token_pda(
		&pool,
		&lp_mint,
		&TOKEN_2022_PROGRAM_ID,
	);
	let (dex_creator_fee_config, _) =
		crate::dex::pda::find_creator_fee_config_pda(base_mint, quote_mint);

	let migrate = super::instructions::Migrate {
		caller: *p.caller,
		bonding_curve,
		base_mint: *base_mint,
		quote_mint: *quote_mint,
		base_vault,
		quote_vault,
		dex_program: DEX_PROGRAM_ID,
		migration_authority: super::pda::MIGRATION_AUTHORITY_ADDRESS,
		pool,
		lp_mint,
		pool_base_vault,
		pool_quote_vault,
		pool_lp_account,
		dex_event_authority: crate::dex::pda::EVENT_AUTHORITY_ADDRESS,
		nexus_program: NEXUS_PROGRAM_ID,
		creator_fee_config,
		base_token_program: TOKEN_2022_PROGRAM_ID,
		quote_token_program: *quote_token_program,
		associated_token_program: ATA_PROGRAM_ID,
		system_program: SYSTEM_PROGRAM_ID,
		event_authority: super::pda::EVENT_AUTHORITY_ADDRESS,
		program: LAUNCHPAD_PROGRAM_ID,
		dex_creator_fee_config,
	};
	let from_curve = |source, mint, destination| HookLeg {
		source,
		mint,
		destination,
		authority: bonding_curve,
		destination_owner: pool,
	};
	let legs = HookLegs {
		legs: vec![
			from_curve(quote_vault, *quote_mint, pool_quote_vault),
			from_curve(base_vault, *base_mint, pool_base_vault),
		],
		written: Vec::new(),
	};
	(migrate.instruction(), legs)
}

#[cfg(test)]
mod tests {
	use solana_address::Address;

	use super::{BuildMigrateParams, build_migrate_instruction};
	use crate::constants::{
		DEX_PROGRAM_ID, LAUNCHPAD_PROGRAM_ID, TOKEN_2022_PROGRAM_ID,
		TOKEN_DECIMALS, WSOL_MINT, WSOL_MINT_INFO,
	};
	use crate::utils::MintInfo;

	const CREATOR_FEE_CONFIG_INDEX: usize = 15;
	const DEX_CREATOR_FEE_CONFIG_INDEX: usize = 22;

	const BASE_MINT: Address = Address::new_from_array([2u8; 32]);

	fn migrate() -> solana_instruction::Instruction {
		let caller = Address::new_from_array([1u8; 32]);
		build_migrate_instruction(&BuildMigrateParams {
			caller: &caller,
			base_mint_info: &MintInfo::plain(
				BASE_MINT,
				TOKEN_2022_PROGRAM_ID,
				TOKEN_DECIMALS,
			),
			quote_mint_info: &WSOL_MINT_INFO,
		})
		.unwrap()
	}

	fn creator_fee_config_pda(program: &Address) -> Address {
		Address::find_program_address(
			&[
				b"creator_fee_config",
				BASE_MINT.as_ref(),
				WSOL_MINT.as_ref(),
			],
			program,
		)
		.0
	}

	#[test]
	fn the_creator_fee_config_slot_is_the_curves_pda() {
		let ix = migrate();

		assert_eq!(ix.accounts.len(), 23);
		let slot = &ix.accounts[CREATOR_FEE_CONFIG_INDEX];
		assert_eq!(slot.pubkey, creator_fee_config_pda(&LAUNCHPAD_PROGRAM_ID));
		assert_eq!(
			slot.pubkey,
			solana_address::address!(
				"BMrF9UWCQwk4bYq9nYsfLWFCVx6UhgLXrxJCABENvd19"
			),
		);
		assert!(!slot.is_signer);
		assert!(slot.is_writable);
	}

	#[test]
	fn the_last_slot_is_the_pools_creator_fee_config_pda() {
		let ix = migrate();

		let slot = &ix.accounts[DEX_CREATOR_FEE_CONFIG_INDEX];
		assert_eq!(slot.pubkey, creator_fee_config_pda(&DEX_PROGRAM_ID));
		assert_eq!(
			slot.pubkey,
			solana_address::address!(
				"DBtuuqp2KVdSJG6ffEd3NiLwhuBwybSXANx7YtjUsG6w"
			),
		);
		assert!(!slot.is_signer);
		assert!(slot.is_writable);
	}

	#[test]
	fn the_curves_and_the_pools_slots_differ() {
		let ix = migrate();

		assert_ne!(
			ix.accounts[CREATOR_FEE_CONFIG_INDEX].pubkey,
			ix.accounts[DEX_CREATOR_FEE_CONFIG_INDEX].pubkey,
		);
	}
}