Swap
The swap instruction performs two main tasks:
Calculate the amount of
mint_xthat we're going to receive by sending a certain amount ofmint_yinto the amm (and vice versa) including the fee.Transfer the
fromtoken to the vault and thetotoken to the user token account
Required Accounts
Below are the accounts required for this context:
user: The user that is swapping the token into the liquidity of the Amm. Must be asigner.user_x_ata: The user's associated token account for token X. This is the account that will receive or send token X into the pool. Must be passed asmutable.user_y_ata: The user's associated token account for token Y. This is the account that will receive or send token Y into the pool. Must be passed asmutable.vault_x: The token account that holds all of token X deposited into the pool. Must be passed asmutable.vault_y: The token account that holds all of token Y deposited into the pool. Must be passed asmutable.config: The configuration account for the AMM pool. Stores all relevant pool parameters and state.token program: The SPL Token program account. This is required to perform token operations such as transfers and minting. Must beexecutable.
Here, again, I’ll leave the implementation to you:
pub struct SwapAccounts<'a> {
pub user: &'a AccountInfo,
pub user_x_ata: &'a AccountInfo,
pub user_y_ata: &'a AccountInfo,
pub vault_x: &'a AccountInfo,
pub vault_y: &'a AccountInfo,
pub config: &'a AccountInfo,
pub token_program: &'a AccountInfo,
}
impl<'a> TryFrom<&'a [AccountInfo]> for SwapAccounts<'a> {
type Error = ProgramError;
fn try_from(accounts: &'a [AccountInfo]) -> Result<Self, Self::Error> {
//..
}
}Instruction Data
Here's the instruction data we need to pass in:
is_x: If this swap is being performed from token X to token Y or vice versa; needed to line up the accounts correctly. Must be a[u8]amount: The amount of token the user is willing to send in exchange of the other token on the pair. Must be a[u64]min: The min amount of token that the user is willing to receive in the exchange of theamount. Must be a[u64]expiration: The expiration of this order. Important to make sure that the transaction has to be played in a certain amount of time. Must be a[i64]
We're going to handle the implementation for the SwapInstructionData same as initialization. So I’ll leave the implementation to you:
pub struct SwapInstructionData {
pub is_x: bool,
pub amount: u64,
pub min: u64,
pub expiration: i64,
}
impl<'a> TryFrom<&'a [u8]> for SwapInstructionData {
type Error = ProgramError;
fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
//..
}
}Instruction Logic
We begin by deserializing both the instruction_data and the accounts.
We then need to:
Load the
Configaccount to grab all the data inside of it. We can do so using theConfig::load()helper.Verify that the
AmmStateis valid (so if it's equal toAmmState::Initialized).Check the derivation of
vault_xandvault_yto be Associated Token Accounts.Deserialize all the token accounts involved and use the data inside of them to calculate the amount to swap using the
constant-product-curvecrate and checking for slippage like this:
// Deserialize the token accounts
let vault_x = unsafe { TokenAccount::from_account_info_unchecked(self.accounts.vault_x)? };
let vault_y = unsafe { TokenAccount::from_account_info_unchecked(self.accounts.vault_y)? };
// Swap Calculations
let mut curve = ConstantProduct::init(
vault_x.amount(),
vault_y.amount(),
vault_x.amount(),
config.fee(),
None,
)
.map_err(|_| ProgramError::Custom(1))?;
let p = match self.instruction_data.is_x {
true => LiquidityPair::X,
false => LiquidityPair::Y,
};
let swap_result = curve
.swap(p, self.instruction_data.amount, self.instruction_data.min)
.map_err(|_| ProgramError::Custom(1))?;
// Check for correct values
if swap_result.deposit == 0 || swap_result.withdraw == 0 {
return Err(ProgramError::InvalidArgument);
}Create the transfer logic checking the
is_xvalue and transfer thefromamounts to the vaults and thetoamounts to the user token accounts like this:
if self.instruction_data.is_x {
Transfer {
//...
}
.invoke()?;
Transfer {
//...
}
.invoke_signed(&signer_seeds)?;
} else {
Transfer {
//...
}
.invoke()?;
Transfer {
//...
}
.invoke_signed(&signer_seeds)?;
}You should be proficient enough to do this on your own, so I’ll leave the implementation to you:
pub struct Swap<'a> {
pub accounts: SwapAccounts<'a>,
pub instruction_data: SwapInstructionData,
}
impl<'a> TryFrom<(&'a [u8], &'a [AccountInfo])> for Swap<'a> {
type Error = ProgramError;
fn try_from((data, accounts): (&'a [u8], &'a [AccountInfo])) -> Result<Self, Self::Error> {
let accounts = SwapAccounts::try_from(accounts)?;
let instruction_data = SwapInstructionData::try_from(data)?;
// Return the initialized struct
Ok(Self {
accounts,
instruction_data,
})
}
}
impl<'a> Swap<'a> {
pub const DISCRIMINATOR: &'a u8 = &3;
pub fn process(&mut self) -> ProgramResult {
//..
Ok(())
}
}