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:
rust
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:
rust
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:
rust
// 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:
rust
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:
rust
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(())
}
}