triad_protocol/instructions/
close_position.rs

1use crate::constraints::{ is_authority_for_user_position, is_token_mint_for_vault };
2use crate::errors::TriadProtocolError;
3use crate::events::ClosePositionRecord;
4use crate::state::Vault;
5use crate::{ ClosePositionArgs, Position, Ticker, UserPosition };
6use anchor_lang::prelude::*;
7use anchor_spl::token::{ self, Token, TokenAccount, Transfer };
8
9#[derive(Accounts)]
10#[instruction(args: ClosePositionArgs)]
11pub struct ClosePosition<'info> {
12    #[account(mut)]
13    pub signer: Signer<'info>,
14
15    #[account(mut)]
16    pub ticker: Account<'info, Ticker>,
17
18    #[account(mut)]
19    pub vault: Account<'info, Vault>,
20
21    #[account(
22        mut,
23        constraint = is_authority_for_user_position(&user_position, &signer)?,
24    )]
25    pub user_position: Account<'info, UserPosition>,
26
27    #[account(mut)]
28    pub vault_token_account: Account<'info, TokenAccount>,
29
30    #[account(
31        mut,
32        token::authority = user_position.authority,
33        token::mint = vault_token_account.mint,
34        constraint = is_token_mint_for_vault(&vault_token_account.mint, &user_token_account.mint)?,
35    )]
36    pub user_token_account: Account<'info, TokenAccount>,
37
38    pub system_program: Program<'info, System>,
39
40    pub token_program: Program<'info, Token>,
41}
42
43pub fn close_position(ctx: Context<ClosePosition>, args: ClosePositionArgs) -> Result<()> {
44    let user_position_cloned = ctx.accounts.user_position.clone();
45
46    let current_pubkey_position = user_position_cloned.positions[args.position_index as usize];
47
48    if current_pubkey_position.amount == 0 {
49        return Err(TriadProtocolError::InvalidPosition.into());
50    }
51
52    let pnl =
53        (ctx.accounts.ticker.price - current_pubkey_position.entry_price) *
54        current_pubkey_position.amount;
55
56    let new_amount = current_pubkey_position.amount + pnl;
57
58    let seeds: &[&[&[u8]]] = &[
59        &[b"vault", ctx.accounts.vault.ticker_address.as_ref(), &[ctx.accounts.vault.bump]],
60    ];
61
62    let transfer = token::transfer(
63        CpiContext::new_with_signer(
64            ctx.accounts.token_program.to_account_info(),
65            Transfer {
66                from: ctx.accounts.vault_token_account.to_account_info(),
67                to: ctx.accounts.user_token_account.to_account_info(),
68                authority: ctx.accounts.vault.to_account_info(),
69            },
70            seeds
71        ),
72        new_amount - (new_amount * 10) / 1000
73    );
74
75    if transfer.is_err() {
76        return Err(TriadProtocolError::InvalidWithdrawAmount.into());
77    }
78
79    let vault = &mut ctx.accounts.vault;
80
81    if current_pubkey_position.is_long {
82        vault.long_balance -= current_pubkey_position.amount;
83        vault.long_positions_opened -= 1;
84    } else {
85        vault.short_balance -= current_pubkey_position.amount;
86        vault.short_positions_opened -= 1;
87    }
88
89    let user_position = &mut ctx.accounts.user_position;
90
91    vault.total_withdrawn += current_pubkey_position.amount;
92    vault.net_withdraws += 1;
93
94    user_position.total_withdrawn += current_pubkey_position.amount;
95    user_position.lp_share -= current_pubkey_position.amount;
96
97    user_position.positions[args.position_index as usize] = Position {
98        amount: 0,
99        entry_price: 0,
100        ts: 0,
101        is_long: false,
102        is_open: false,
103        pnl: 0,
104    };
105
106    emit!(ClosePositionRecord {
107        ticker: ctx.accounts.vault.ticker_address,
108        close_price: ctx.accounts.ticker.price,
109        ts: Clock::get()?.unix_timestamp,
110        pnl: (ctx.accounts.ticker.price as i64) - (current_pubkey_position.entry_price as i64),
111        user: user_position.authority,
112        amount: current_pubkey_position.amount,
113        is_long: current_pubkey_position.is_long,
114    });
115
116    Ok(())
117}