Rust
Pinocchio AMM

Pinocchio AMM

13 Graduates

Withdraw

The withdraw instruction performs three main tasks:

  • Withdraw the mint_x and mint_y token based on the amount of LP the user wants to burn.

  • Calculate the amount to withdraw and check that the amount isn't less than mint_x and mint_y designated by the user.

  • Burn the right amount of mint_lp from the user ata.

As mentioned in the initialize instruction section; we're going to initialize all the Associated Token Accounts outside of our instruction for optimization purposes.

Required Accounts

Below are the accounts required for this context:

  • user: The user that is withdrawing the token into the liquidity of the Amm. Must be a signer.

  • mint_lp: The Mint account that will represent the pool’s liquidity. Must be passed as mutable.

  • vault_x: The token account that holds all of token X deposited into the pool. Must be passed as mutable.

  • vault_y: The token account that holds all of token Y deposited into the pool. Must be passed as mutable.

  • user_x_ata: The user's associated token account for token X. This is the destination account to which the user's token X will be transferred from the pool. Must be passed as mutable.

  • user_y_ata: The user's associated token account for token Y. This is the destination account to which the user's token Y will be transferred from the pool. Must be passed as mutable.

  • user_lp_ata: The user's associated token account for LP tokens. This is the source account where LP tokens will be burned. Must be passed as mutable.

  • 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 be executable.

Here, again, I’ll leave the implementation to you:

rust
pub struct WithdrawAccounts<'a> {
    pub user: &'a AccountInfo,
    pub mint_lp: &'a AccountInfo,
    pub vault_x: &'a AccountInfo,
    pub vault_y: &'a AccountInfo,
    pub user_x_ata: &'a AccountInfo,
    pub user_y_ata: &'a AccountInfo,
    pub user_lp_ata: &'a AccountInfo,
    pub config: &'a AccountInfo,
    pub token_program: &'a AccountInfo,
}

impl<'a> TryFrom<&'a [AccountInfo]> for WithdrawAccounts<'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:

  • amount: The amount of LP token that the user wishes to burn. Must be a [u64]

  • min_x: The min amount of token X that the user is willing to withdraw. Must be a [u64]

  • min_y: The min amount of token Y that the user is willing to withdraw. 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 WithdrawInstructionData same as initialization. So I’ll leave the implementation to you:

rust
pub struct WithdrawInstructionData {
    pub amount: u64,
    pub min_x: u64,
    pub min_y: u64,
    pub expiration: i64,
}

impl<'a> TryFrom<&'a [u8]> for WithdrawInstructionData {
    type Error = ProgramError;

    fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
        //..
    }
}

Make sure that any of the amount, like amount, min_y and min_x are greater than zero and that the order has not expired yet using the Clock sysvar.

Instruction Logic

We begin by deserializing both the instruction_data and the accounts.

We then need to:

  • Load the Config account to grab all the data inside of it. We can do so using the Config::load() helper.

  • Verify that the AmmState is valid (so if it's not equal to AmmState::Disabled).

  • Check the derivation of vault_x and vault_y to be Associated Token Accounts.

  • Deserialize all the token accounts involved and use the data inside of them to calculate the amount to withdraw using the constant-product-curve crate and checking for slippage like this:

rust
let mint_lp = unsafe { Mint::from_account_info_unchecked(self.accounts.mint_lp)? };
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)? };

let (x, y) = match mint_lp.supply() == self.instruction_data.amount {
    true => (vault_x.amount(), vault_y.amount()),
    false => {
        let amounts = ConstantProduct::xy_withdraw_amounts_from_l(
            vault_x.amount(),
            vault_y.amount(),
            mint_lp.supply(),
            self.instruction_data.amount,
            6,
        )
        .map_err(|_| ProgramError::InvalidArgument)?;

        (amounts.x, amounts.y)
    }
};

// Check for slippage
if !(x >= self.instruction_data.min_x && y >= self.instruction_data.min_y) {
    return Err(ProgramError::InvalidArgument);
}
  • Transfer the amounts from the vaults to the token accounts of the user and burn the appropriate amount of LP tokens from the user token account

The authority of vault_x and vault_y is the config account

You should be proficient enough to do this on your own, so I’ll leave the implementation to you:

rust
pub struct Withdraw<'a> {
    pub accounts: WithdrawAccounts<'a>,
    pub instruction_data: WithdrawInstructionData,
}

impl<'a> TryFrom<(&'a [u8], &'a [AccountInfo])> for Withdraw<'a> {
    type Error = ProgramError;

    fn try_from((data, accounts): (&'a [u8], &'a [AccountInfo])) -> Result<Self, Self::Error> {
        let accounts = WithdrawAccounts::try_from(accounts)?;
        let instruction_data = WithdrawInstructionData::try_from(data)?;

        // Return the initialized struct
        Ok(Self {
            accounts,
            instruction_data,
        })
    }
}

impl<'a> Withdraw<'a> {
    pub const DISCRIMINATOR: &'a u8 = &2;

    pub fn process(&mut self) -> ProgramResult {
        //..

        Ok(())
    }
}
Next PageSwap
OR SKIP TO THE CHALLENGE
Ready to take the challenge?
Contents
View Source
Blueshift © 2025Commit: e573eab