triad_protocol/instructions/
open_position.rs

1use 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}