Skip to main content

percli_program/instructions/
initialize_market.rs

1use anchor_lang::prelude::*;
2use anchor_spl::token::{Mint, Token, TokenAccount};
3use percli_core::RiskParams;
4
5use crate::error::PercolatorError;
6use crate::state::{engine_from_account_data, write_header, MarketHeader, MARKET_ACCOUNT_SIZE};
7
8#[derive(Accounts)]
9pub struct InitializeMarket<'info> {
10    #[account(mut)]
11    pub authority: Signer<'info>,
12
13    /// CHECK: Manually validated. Initialized as a PDA with the correct size.
14    #[account(
15        init,
16        payer = authority,
17        space = MARKET_ACCOUNT_SIZE,
18        seeds = [b"market", authority.key().as_ref()],
19        bump,
20    )]
21    pub market: UncheckedAccount<'info>,
22
23    /// The SPL token mint for this market's collateral (e.g. USDC).
24    pub mint: Account<'info, Mint>,
25
26    /// Vault token account — holds all deposited collateral for this market.
27    #[account(
28        init,
29        payer = authority,
30        token::mint = mint,
31        token::authority = market,
32        seeds = [b"vault", market.key().as_ref()],
33        bump,
34    )]
35    pub vault: Account<'info, TokenAccount>,
36
37    /// CHECK: Pyth price feed for this market. Stored in header; validated on crank.
38    pub oracle: UncheckedAccount<'info>,
39
40    /// Matcher authority — the only signer allowed to submit trades.
41    /// CHECK: Stored in header. Can be any pubkey (e.g. a program or multisig).
42    pub matcher: UncheckedAccount<'info>,
43
44    pub token_program: Program<'info, Token>,
45    pub system_program: Program<'info, System>,
46}
47
48pub fn handler(
49    ctx: Context<InitializeMarket>,
50    init_slot: u64,
51    init_oracle_price: u64,
52    params: RiskParamsInput,
53) -> Result<()> {
54    let market = &ctx.accounts.market;
55    let mut data = market.try_borrow_mut_data()?;
56
57    // Write discriminator (first 8 bytes) — use a fixed marker
58    data[0..8].copy_from_slice(b"percmrkt");
59
60    let header = MarketHeader {
61        authority: ctx.accounts.authority.key(),
62        mint: ctx.accounts.mint.key(),
63        oracle: ctx.accounts.oracle.key(),
64        matcher: ctx.accounts.matcher.key(),
65        bump: ctx.bumps.market,
66        vault_bump: ctx.bumps.vault,
67        _padding: [0; 6],
68    };
69    write_header(&mut data, &header);
70
71    require!(init_oracle_price > 0, PercolatorError::InvalidOraclePriceValue);
72
73    let engine = engine_from_account_data(&mut data);
74    let risk_params = params.to_risk_params();
75    engine.init_in_place(risk_params, init_slot, init_oracle_price);
76
77    Ok(())
78}
79
80/// Anchor-friendly input for RiskParams (all primitives, no wrapper types).
81#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
82pub struct RiskParamsInput {
83    pub warmup_period_slots: u64,
84    pub maintenance_margin_bps: u64,
85    pub initial_margin_bps: u64,
86    pub trading_fee_bps: u64,
87    pub max_accounts: u64,
88    pub new_account_fee: u64,
89    pub maintenance_fee_per_slot: u64,
90    pub max_crank_staleness_slots: u64,
91    pub liquidation_fee_bps: u64,
92    pub liquidation_fee_cap: u64,
93    pub min_liquidation_abs: u64,
94    pub min_initial_deposit: u64,
95    pub min_nonzero_mm_req: u64,
96    pub min_nonzero_im_req: u64,
97    pub insurance_floor: u64,
98}
99
100impl RiskParamsInput {
101    pub fn to_risk_params(&self) -> RiskParams {
102        use percli_core::U128;
103        RiskParams {
104            warmup_period_slots: self.warmup_period_slots,
105            maintenance_margin_bps: self.maintenance_margin_bps,
106            initial_margin_bps: self.initial_margin_bps,
107            trading_fee_bps: self.trading_fee_bps,
108            max_accounts: self.max_accounts,
109            new_account_fee: U128::new(self.new_account_fee as u128),
110            maintenance_fee_per_slot: U128::new(self.maintenance_fee_per_slot as u128),
111            max_crank_staleness_slots: self.max_crank_staleness_slots,
112            liquidation_fee_bps: self.liquidation_fee_bps,
113            liquidation_fee_cap: U128::new(self.liquidation_fee_cap as u128),
114            min_liquidation_abs: U128::new(self.min_liquidation_abs as u128),
115            min_initial_deposit: U128::new(self.min_initial_deposit as u128),
116            min_nonzero_mm_req: self.min_nonzero_mm_req as u128,
117            min_nonzero_im_req: self.min_nonzero_im_req as u128,
118            insurance_floor: U128::new(self.insurance_floor as u128),
119        }
120    }
121}