1use crate::instruction::{InstructionAccount, InstructionView, Signer};
10use crate::{AccountView, Address, ProgramError, ProgramResult};
11
12pub const TOKEN_2022_PROGRAM_ID: Address = Address::new_from_array(crate::__decode_base58_32(
14 "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
15));
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub enum MintProgram {
20 Legacy,
21 Token2022,
22}
23
24impl MintProgram {
25 pub const fn address(self) -> &'static Address {
26 match self {
27 Self::Legacy => &crate::token::TOKEN_PROGRAM_ID,
28 Self::Token2022 => &TOKEN_2022_PROGRAM_ID,
29 }
30 }
31}
32
33#[derive(Clone, Copy)]
35pub struct MintConfig<'a> {
36 pub decimals: u8,
37 pub mint_authority: &'a Address,
38 pub freeze_authority: Option<&'a Address>,
39}
40
41#[derive(Clone, Copy)]
47pub enum MintExtension<'a> {
48 TransferFeeConfig {
49 authority: Option<&'a Address>,
50 withdraw_authority: Option<&'a Address>,
51 basis_points: u16,
52 maximum_fee: u64,
53 },
54 MintCloseAuthority(Option<&'a Address>),
55 NonTransferable,
56 PermanentDelegate(&'a Address),
57 TransferHook {
58 authority: Option<&'a Address>,
59 program_id: Option<&'a Address>,
60 },
61 MetadataPointer {
62 authority: Option<&'a Address>,
63 metadata_address: Option<&'a Address>,
64 },
65}
66
67impl MintExtension<'_> {
68 pub const fn extension_type(&self) -> u16 {
70 match self {
71 Self::TransferFeeConfig { .. } => 1,
72 Self::MintCloseAuthority(_) => 3,
73 Self::NonTransferable => 9,
74 Self::PermanentDelegate(_) => 12,
75 Self::TransferHook { .. } => 14,
76 Self::MetadataPointer { .. } => 18,
77 }
78 }
79
80 const fn value_len(&self) -> usize {
81 match self {
82 Self::TransferFeeConfig { .. } => 108,
83 Self::MintCloseAuthority(_) | Self::PermanentDelegate(_) => 32,
84 Self::NonTransferable => 0,
85 Self::TransferHook { .. } | Self::MetadataPointer { .. } => 64,
86 }
87 }
88
89 fn validate(&self) -> ProgramResult {
90 fn nullable(value: Option<&Address>) -> ProgramResult {
91 if value.is_some_and(|a| a.as_array() == &[0; 32]) {
92 Err(ProgramError::InvalidArgument)
93 } else {
94 Ok(())
95 }
96 }
97 match *self {
98 Self::TransferFeeConfig {
99 authority,
100 withdraw_authority,
101 basis_points,
102 ..
103 } => {
104 nullable(authority)?;
105 nullable(withdraw_authority)?;
106 if basis_points > 10_000 {
107 return Err(ProgramError::InvalidArgument);
108 }
109 }
110 Self::MintCloseAuthority(authority) => nullable(authority)?,
111 Self::PermanentDelegate(delegate) => nullable(Some(delegate))?,
112 Self::TransferHook {
113 authority,
114 program_id,
115 } => {
116 nullable(authority)?;
117 nullable(program_id)?;
118 }
119 Self::MetadataPointer {
120 authority,
121 metadata_address,
122 } => {
123 nullable(authority)?;
124 nullable(metadata_address)?;
125 }
126 Self::NonTransferable => {}
127 }
128 Ok(())
129 }
130
131 pub fn instruction_data(&self) -> Result<MintInstructionData, ProgramError> {
133 self.validate()?;
134 let mut out = MintInstructionData::new();
135 match *self {
136 Self::TransferFeeConfig {
137 authority,
138 withdraw_authority,
139 basis_points,
140 maximum_fee,
141 } => {
142 out.push(&[26, 0]);
143 out.option(authority);
144 out.option(withdraw_authority);
145 out.push(&basis_points.to_le_bytes());
146 out.push(&maximum_fee.to_le_bytes());
147 }
148 Self::MintCloseAuthority(authority) => {
149 out.push(&[25]);
150 out.option(authority);
151 }
152 Self::NonTransferable => out.push(&[32]),
153 Self::PermanentDelegate(delegate) => {
154 out.push(&[35]);
155 out.push(delegate.as_array());
156 }
157 Self::TransferHook {
158 authority,
159 program_id,
160 } => {
161 out.push(&[36, 0]);
162 out.nullable(authority);
163 out.nullable(program_id);
164 }
165 Self::MetadataPointer {
166 authority,
167 metadata_address,
168 } => {
169 out.push(&[39, 0]);
170 out.nullable(authority);
171 out.nullable(metadata_address);
172 }
173 }
174 Ok(out)
175 }
176}
177
178pub struct MintInstructionData {
180 bytes: [u8; 78],
181 len: usize,
182}
183
184impl MintInstructionData {
185 fn new() -> Self {
186 Self {
187 bytes: [0; 78],
188 len: 0,
189 }
190 }
191 fn push(&mut self, bytes: &[u8]) {
192 self.bytes[self.len..self.len + bytes.len()].copy_from_slice(bytes);
193 self.len += bytes.len();
194 }
195 fn option(&mut self, address: Option<&Address>) {
196 self.push(&[u8::from(address.is_some())]);
197 if let Some(address) = address {
198 self.push(address.as_array());
199 }
200 }
201 fn nullable(&mut self, address: Option<&Address>) {
202 self.push(address.map_or(&[0; 32][..], |a| &a.as_array()[..]));
203 }
204 pub fn as_bytes(&self) -> &[u8] {
205 &self.bytes[..self.len]
206 }
207}
208
209impl MintConfig<'_> {
210 pub fn instruction_data(&self) -> MintInstructionData {
212 let mut out = MintInstructionData::new();
213 out.push(&[20, self.decimals]);
214 out.push(self.mint_authority.as_array());
215 out.option(self.freeze_authority);
216 out
217 }
218}
219
220pub struct InitializeMint2<'a, 'b> {
227 pub mint: &'a AccountView<'a>,
228 pub program: MintProgram,
229 pub config: MintConfig<'b>,
230}
231
232impl InitializeMint2<'_, '_> {
233 pub fn invoke(&self) -> ProgramResult {
234 invoke_mint(
235 self.mint,
236 self.program,
237 self.config.instruction_data().as_bytes(),
238 )
239 }
240}
241
242fn invoke_mint(mint: &AccountView<'_>, program: MintProgram, data: &[u8]) -> ProgramResult {
243 if !mint.owned_by(program.address()) {
244 return Err(ProgramError::IncorrectProgramId);
245 }
246 if !mint.is_writable() {
247 return Err(ProgramError::InvalidArgument);
248 }
249 let accounts = [InstructionAccount::writable(mint.address())];
250 let instruction = InstructionView {
251 program_id: program.address(),
252 accounts: &accounts,
253 data,
254 };
255 crate::cpi::invoke(&instruction, &[mint])
256}
257
258pub struct MintPlan<'a> {
265 program: MintProgram,
266 config: MintConfig<'a>,
267 extensions: &'a [MintExtension<'a>],
268 space: usize,
269}
270
271impl<'a> MintPlan<'a> {
272 pub fn new(
273 program: MintProgram,
274 config: MintConfig<'a>,
275 extensions: &'a [MintExtension<'a>],
276 ) -> Result<Self, ProgramError> {
277 if program == MintProgram::Legacy && !extensions.is_empty() {
278 return Err(ProgramError::InvalidArgument);
279 }
280 let mut seen = 0u32;
281 let mut space = if extensions.is_empty() { 82 } else { 166 };
282 for extension in extensions {
283 extension.validate()?;
284 let mask = 1u32 << extension.extension_type();
285 if seen & mask != 0 {
286 return Err(ProgramError::InvalidArgument);
287 }
288 seen |= mask;
289 space += 4 + extension.value_len();
290 }
291 if space == 355 {
293 space += 2;
294 }
295 Ok(Self {
296 program,
297 config,
298 extensions,
299 space,
300 })
301 }
302
303 pub const fn space(&self) -> usize {
304 self.space
305 }
306
307 pub fn check_space(&self, space: usize) -> ProgramResult {
309 if space != self.space {
310 return Err(ProgramError::InvalidAccountData);
311 }
312 Ok(())
313 }
314
315 pub fn initialize(&self, mint: &AccountView<'_>) -> ProgramResult {
320 if !mint.owned_by(self.program.address()) {
321 return Err(ProgramError::IncorrectProgramId);
322 }
323 if !mint.is_writable() {
324 return Err(ProgramError::InvalidArgument);
325 }
326 self.check_space(mint.data_len())?;
327 if mint.lamports() < crate::rent::minimum_balance_live(self.space)? {
328 return Err(ProgramError::AccountNotRentExempt);
329 }
330 {
331 let data = mint.try_borrow()?;
332 if data.iter().any(|b| *b != 0) {
333 return Err(ProgramError::AccountAlreadyInitialized);
334 }
335 }
336 for extension in self.extensions {
337 invoke_mint(mint, self.program, extension.instruction_data()?.as_bytes())?;
338 }
339 InitializeMint2 {
340 mint,
341 program: self.program,
342 config: self.config,
343 }
344 .invoke()
345 }
346
347 pub fn create(
355 &self,
356 payer: &AccountView<'_>,
357 mint: &AccountView<'_>,
358 signers: &[Signer<'_, '_>],
359 ) -> ProgramResult {
360 if payer.address() == mint.address() || !payer.is_writable() || !mint.is_writable() {
361 return Err(ProgramError::InvalidArgument);
362 }
363 if !mint.owned_by(&crate::system::SYSTEM_PROGRAM_ID)
364 || !payer.owned_by(&crate::system::SYSTEM_PROGRAM_ID)
365 {
366 return Err(ProgramError::IncorrectProgramId);
367 }
368 if mint.data_len() != 0 || payer.data_len() != 0 {
369 return Err(ProgramError::InvalidAccountData);
370 }
371 let rent = crate::rent::minimum_balance_live(self.space)?;
372 let funding = rent.saturating_sub(mint.lamports());
373 if payer.lamports() < funding {
374 return Err(ProgramError::InsufficientFunds);
375 }
376 crate::system::CreateAccountAllowPrefund {
377 to: mint,
378 funding: Some((payer, funding)),
379 space: self.space as u64,
380 owner: self.program.address(),
381 }
382 .invoke_signed(signers)?;
383 self.initialize(mint)
384 }
385}