dloom_flow/dlmm/instructions/
open_position.rs

1// FILE: programs/dloom_flow/src/instructions/dlmm_open_position.rs
2
3use crate::{
4    constants::MAX_BINS_PER_POSITION,
5    errors::DloomError,
6    events::DlmmPositionOpened,
7    dlmm::{state::{DlmmPool, Position}},
8};
9use anchor_lang::prelude::*;
10use anchor_spl::{
11    associated_token::AssociatedToken,
12    token_interface::{self, Mint, MintTo, TokenAccount, TokenInterface},
13};
14use mpl_token_metadata::{
15    accounts::{MasterEdition, Metadata},
16    instructions::{
17        CreateMasterEditionV3Cpi, CreateMasterEditionV3CpiAccounts,
18        CreateMasterEditionV3InstructionArgs, CreateMetadataAccountV3Cpi,
19        CreateMetadataAccountV3CpiAccounts, CreateMetadataAccountV3InstructionArgs,
20    },
21    types::DataV2,
22    ID as TOKEN_METADATA_ID,
23};
24
25/// Handler for the `dlmm_open_position` instruction.
26pub fn handle_dlmm_open_position(
27    ctx: Context<DlmmOpenPosition>,
28    lower_bin_id: i32,
29    upper_bin_id: i32,
30) -> Result<()> {
31    // 1. Validate the bin range.
32    require!(lower_bin_id < upper_bin_id, DloomError::InvalidBinRange);
33    let bin_step = ctx.accounts.dlmm_pool.bin_step as i32;
34    require!(
35        lower_bin_id % bin_step == 0 && upper_bin_id % bin_step == 0,
36        DloomError::InvalidBinId
37    );
38
39    let range = (upper_bin_id - lower_bin_id) / bin_step as i32;
40    require!(range <= MAX_BINS_PER_POSITION, DloomError::RangeTooWide);
41
42    // 2. Initialize the Position account.
43    let position = &mut ctx.accounts.position;
44    position.pool = ctx.accounts.dlmm_pool.key(); // Reference the DLMM pool
45    position.owner = ctx.accounts.owner.key();
46    position.lower_bin_id = lower_bin_id;
47    position.upper_bin_id = upper_bin_id;
48    position.liquidity = 0; // Positions are created with zero liquidity
49    position.position_mint = ctx.accounts.position_mint.key();
50    position.fee_growth_snapshot_a = 0;
51    position.fee_growth_snapshot_b = 0;
52
53    // 3. Mint the Position NFT to the user.
54    token_interface::mint_to(
55        CpiContext::new(
56            ctx.accounts.token_program.to_account_info(),
57            MintTo {
58                mint: ctx.accounts.position_mint.to_account_info(),
59                to: ctx.accounts.user_position_nft_account.to_account_info(),
60                authority: ctx.accounts.owner.to_account_info(),
61            },
62        ),
63        1, // It's an NFT, so only 1 is minted.
64    )?;
65
66    let base_uri = "https://api.yourprotocol.com/metadata/";
67    let mint_key_string = ctx.accounts.position_mint.key().to_string();
68    let dynamic_uri = format!("{}{}", base_uri, mint_key_string);
69    // 4. Create the metadata account for the Position NFT (Metaplex CPI).
70    CreateMetadataAccountV3Cpi::new(
71        &ctx.accounts.token_metadata_program,
72        CreateMetadataAccountV3CpiAccounts {
73            metadata: &ctx.accounts.metadata_account,
74            mint: &ctx.accounts.position_mint.to_account_info(),
75            mint_authority: &ctx.accounts.owner.to_account_info(),
76            payer: &ctx.accounts.owner.to_account_info(),
77            update_authority: (&ctx.accounts.owner.to_account_info(), true),
78            system_program: &ctx.accounts.system_program,
79            rent: Some(&ctx.accounts.rent.to_account_info()),
80        },
81        CreateMetadataAccountV3InstructionArgs {
82            data: DataV2 {
83                name: "DLMM Position".to_string(),
84                symbol: "DLMMLP".to_string(),
85                uri: dynamic_uri,
86                seller_fee_basis_points: 0,
87                creators: None,
88                collection: None,
89                uses: None,
90            },
91            is_mutable: true,
92            collection_details: None,
93        },
94    )
95    .invoke()?;
96
97    // 5. Create the master edition account to make it a true NFT (Metaplex CPI).
98    CreateMasterEditionV3Cpi::new(
99        &ctx.accounts.token_metadata_program,
100        CreateMasterEditionV3CpiAccounts {
101            edition: &ctx.accounts.master_edition_account,
102            mint: &ctx.accounts.position_mint.to_account_info(),
103            update_authority: &ctx.accounts.owner.to_account_info(),
104            mint_authority: &ctx.accounts.owner.to_account_info(),
105            payer: &ctx.accounts.owner.to_account_info(),
106            metadata: &ctx.accounts.metadata_account,
107            token_program: &ctx.accounts.token_program,
108            system_program: &ctx.accounts.system_program,
109            rent: Some(&ctx.accounts.rent.to_account_info()),
110        },
111        CreateMasterEditionV3InstructionArgs {
112            max_supply: Some(0),
113        },
114    )
115    .invoke()?;
116
117    emit!(DlmmPositionOpened {
118        pool_address: ctx.accounts.dlmm_pool.key(),
119        owner: ctx.accounts.owner.key(),
120        position_address: ctx.accounts.position.key(),
121        position_mint: ctx.accounts.position_mint.key(),
122        lower_bin_id,
123        upper_bin_id,
124    });
125
126    Ok(())
127}
128
129#[derive(Accounts)]
130pub struct DlmmOpenPosition<'info> {
131    #[account(mut)]
132    pub owner: Signer<'info>,
133
134    // Use the DlmmPool struct
135    pub dlmm_pool: Box<Account<'info, DlmmPool>>,
136
137    #[account(
138        init,
139        payer = owner,
140        space = 8 + 256, // Allocate space for Position struct
141        seeds = [b"position", position_mint.key().as_ref()],
142        bump
143    )]
144    pub position: Box<Account<'info, Position>>,
145
146    #[account(
147        init,
148        payer = owner,
149        mint::decimals = 0,
150        mint::authority = owner,
151        mint::freeze_authority = owner
152    )]
153    pub position_mint: InterfaceAccount<'info, Mint>,
154
155    #[account(
156        init,
157        payer = owner,
158        associated_token::mint = position_mint,
159        associated_token::authority = owner
160    )]
161    pub user_position_nft_account: InterfaceAccount<'info, TokenAccount>,
162
163    // Metadata accounts required by the Metaplex Token Metadata program
164    #[account(mut, address = Metadata::find_pda(&position_mint.key()).0)]
165    /// CHECK: Address is checked via constraint.
166    pub metadata_account: AccountInfo<'info>,
167
168    #[account(mut, address = MasterEdition::find_pda(&position_mint.key()).0)]
169    /// CHECK: Address is checked via constraint.
170    pub master_edition_account: AccountInfo<'info>,
171
172    pub system_program: Program<'info, System>,
173    pub token_program: Interface<'info, TokenInterface>,
174    pub associated_token_program: Program<'info, AssociatedToken>,
175    #[account(address = TOKEN_METADATA_ID)]
176    /// CHECK: This is the Metaplex program ID.
177    pub token_metadata_program: AccountInfo<'info>,
178    pub rent: Sysvar<'info, Rent>,
179}