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