triad_protocol/instructions/
close_position.rs1use 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}