1use std::str::FromStr;
2
3pub use solana_account_decoder_client_types_wasm::token::TokenAccountType;
4pub use solana_account_decoder_client_types_wasm::token::UiAccountState;
5pub use solana_account_decoder_client_types_wasm::token::UiMint;
6pub use solana_account_decoder_client_types_wasm::token::UiMultisig;
7pub use solana_account_decoder_client_types_wasm::token::UiTokenAccount;
8pub use solana_account_decoder_client_types_wasm::token::UiTokenAmount;
9pub use solana_account_decoder_client_types_wasm::token::real_number_string;
10pub use solana_account_decoder_client_types_wasm::token::real_number_string_trimmed;
11use solana_program_option::COption;
12use solana_program_pack::Pack;
13use solana_pubkey::Pubkey;
14pub use spl_generic_token::is_known_spl_token_id;
15pub use spl_generic_token::spl_token_ids;
16use spl_token_2022_interface::extension::BaseStateWithExtensions;
17use spl_token_2022_interface::extension::StateWithExtensions;
18use spl_token_2022_interface::generic_token_account::GenericTokenAccount;
19use spl_token_2022_interface::state::Account;
20use spl_token_2022_interface::state::AccountState;
21use spl_token_2022_interface::state::Mint;
22use spl_token_2022_interface::state::Multisig;
23
24use crate::parse_account_data::ParsableAccount;
25use crate::parse_account_data::ParseAccountError;
26use crate::parse_account_data::SplTokenAdditionalDataV2;
27use crate::parse_token_extension::parse_extension;
28
29pub fn parse_token_v3(
30 data: &[u8],
31 additional_data: Option<&SplTokenAdditionalDataV2>,
32) -> Result<TokenAccountType, ParseAccountError> {
33 if let Ok(account) = StateWithExtensions::<Account>::unpack(data) {
34 let additional_data = additional_data.as_ref().ok_or_else(|| {
35 ParseAccountError::AdditionalDataMissing(
36 "no mint_decimals provided to parse spl-token account".to_string(),
37 )
38 })?;
39 let extension_types = account.get_extension_types().unwrap_or_default();
40 let ui_extensions = extension_types
41 .iter()
42 .map(|extension_type| parse_extension::<Account>(extension_type, &account))
43 .collect();
44 return Ok(TokenAccountType::Account(UiTokenAccount {
45 mint: account.base.mint.to_string(),
46 owner: account.base.owner.to_string(),
47 token_amount: token_amount_to_ui_amount_v3(account.base.amount, additional_data),
48 delegate: match account.base.delegate {
49 COption::Some(pubkey) => Some(pubkey.to_string()),
50 COption::None => None,
51 },
52 state: convert_account_state(account.base.state),
53 is_native: account.base.is_native(),
54 rent_exempt_reserve: match account.base.is_native {
55 COption::Some(reserve) => {
56 Some(token_amount_to_ui_amount_v3(reserve, additional_data))
57 }
58 COption::None => None,
59 },
60 delegated_amount: if account.base.delegate.is_none() {
61 None
62 } else {
63 Some(token_amount_to_ui_amount_v3(
64 account.base.delegated_amount,
65 additional_data,
66 ))
67 },
68 close_authority: match account.base.close_authority {
69 COption::Some(pubkey) => Some(pubkey.to_string()),
70 COption::None => None,
71 },
72 extensions: ui_extensions,
73 }));
74 }
75 if let Ok(mint) = StateWithExtensions::<Mint>::unpack(data) {
76 let extension_types = mint.get_extension_types().unwrap_or_default();
77 let ui_extensions = extension_types
78 .iter()
79 .map(|extension_type| parse_extension::<Mint>(extension_type, &mint))
80 .collect();
81 return Ok(TokenAccountType::Mint(UiMint {
82 mint_authority: match mint.base.mint_authority {
83 COption::Some(pubkey) => Some(pubkey.to_string()),
84 COption::None => None,
85 },
86 supply: mint.base.supply.to_string(),
87 decimals: mint.base.decimals,
88 is_initialized: mint.base.is_initialized,
89 freeze_authority: match mint.base.freeze_authority {
90 COption::Some(pubkey) => Some(pubkey.to_string()),
91 COption::None => None,
92 },
93 extensions: ui_extensions,
94 }));
95 }
96 if data.len() == Multisig::get_packed_len() {
97 let multisig = Multisig::unpack(data)
98 .map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
99 Ok(TokenAccountType::Multisig(UiMultisig {
100 num_required_signers: multisig.m,
101 num_valid_signers: multisig.n,
102 is_initialized: multisig.is_initialized,
103 signers: multisig
104 .signers
105 .iter()
106 .filter_map(|pubkey| {
107 if pubkey == &Pubkey::default() {
108 None
109 } else {
110 Some(pubkey.to_string())
111 }
112 })
113 .collect(),
114 }))
115 } else {
116 Err(ParseAccountError::AccountNotParsable(
117 ParsableAccount::SplToken,
118 ))
119 }
120}
121
122pub fn convert_account_state(state: AccountState) -> UiAccountState {
123 match state {
124 AccountState::Uninitialized => UiAccountState::Uninitialized,
125 AccountState::Initialized => UiAccountState::Initialized,
126 AccountState::Frozen => UiAccountState::Frozen,
127 }
128}
129
130pub fn token_amount_to_ui_amount_v3(
131 amount: u64,
132 additional_data: &SplTokenAdditionalDataV2,
133) -> UiTokenAmount {
134 let decimals = additional_data.decimals;
135 let (ui_amount, ui_amount_string) = if let Some((interest_bearing_config, unix_timestamp)) =
136 additional_data.interest_bearing_config
137 {
138 let ui_amount_string =
139 interest_bearing_config.amount_to_ui_amount(amount, decimals, unix_timestamp);
140 (
141 ui_amount_string
142 .as_ref()
143 .and_then(|x| f64::from_str(x).ok()),
144 ui_amount_string.unwrap_or(String::new()),
145 )
146 } else if let Some((scaled_ui_amount_config, unix_timestamp)) =
147 additional_data.scaled_ui_amount_config
148 {
149 let ui_amount_string =
150 scaled_ui_amount_config.amount_to_ui_amount(amount, decimals, unix_timestamp);
151 (
152 ui_amount_string
153 .as_ref()
154 .and_then(|x| f64::from_str(x).ok()),
155 ui_amount_string.unwrap_or(String::new()),
156 )
157 } else {
158 let ui_amount = 10_usize
159 .checked_pow(u32::from(decimals))
160 .map(|dividend| amount as f64 / dividend as f64);
161 (ui_amount, real_number_string_trimmed(amount, decimals))
162 };
163 UiTokenAmount {
164 ui_amount,
165 decimals,
166 amount: amount.to_string(),
167 ui_amount_string,
168 }
169}
170
171pub fn get_token_account_mint(data: &[u8]) -> Option<Pubkey> {
172 Account::valid_account_data(data)
173 .then(|| Pubkey::try_from(data.get(..32)?).ok())
174 .flatten()
175}
176
177#[cfg(test)]
178mod test {
179 use solana_account_decoder_client_types_wasm::token::UiExtension;
180 use spl_token_2022_interface::extension::BaseStateWithExtensionsMut;
181 use spl_token_2022_interface::extension::ExtensionType;
182 use spl_token_2022_interface::extension::StateWithExtensionsMut;
183 use spl_token_2022_interface::extension::immutable_owner::ImmutableOwner;
184 use spl_token_2022_interface::extension::interest_bearing_mint::InterestBearingConfig;
185 use spl_token_2022_interface::extension::memo_transfer::MemoTransfer;
186 use spl_token_2022_interface::extension::mint_close_authority::MintCloseAuthority;
187 use spl_token_2022_interface::extension::scaled_ui_amount::ScaledUiAmountConfig;
188
189 use super::*;
190 use crate::parse_token_extension::UiMemoTransfer;
191 use crate::parse_token_extension::UiMintCloseAuthority;
192
193 const INT_SECONDS_PER_YEAR: i64 = 6 * 6 * 24 * 36524;
194
195 #[test]
196 fn test_parse_token() {
197 let mint_pubkey = Pubkey::new_from_array([2; 32]);
198 let owner_pubkey = Pubkey::new_from_array([3; 32]);
199 let mut account_data = vec![0; Account::get_packed_len()];
200 let mut account = Account::unpack_unchecked(&account_data).unwrap();
201 account.mint = mint_pubkey;
202 account.owner = owner_pubkey;
203 account.amount = 42;
204 account.state = AccountState::Initialized;
205 account.is_native = COption::None;
206 account.close_authority = COption::Some(owner_pubkey);
207 Account::pack(account, &mut account_data).unwrap();
208
209 assert!(parse_token_v3(&account_data, None).is_err());
210 assert_eq!(
211 parse_token_v3(
212 &account_data,
213 Some(&SplTokenAdditionalDataV2::with_decimals(2))
214 )
215 .unwrap(),
216 TokenAccountType::Account(UiTokenAccount {
217 mint: mint_pubkey.to_string(),
218 owner: owner_pubkey.to_string(),
219 token_amount: UiTokenAmount {
220 ui_amount: Some(0.42),
221 decimals: 2,
222 amount: "42".to_string(),
223 ui_amount_string: "0.42".to_string()
224 },
225 delegate: None,
226 state: UiAccountState::Initialized,
227 is_native: false,
228 rent_exempt_reserve: None,
229 delegated_amount: None,
230 close_authority: Some(owner_pubkey.to_string()),
231 extensions: vec![],
232 }),
233 );
234
235 let mut mint_data = vec![0; Mint::get_packed_len()];
236 let mut mint = Mint::unpack_unchecked(&mint_data).unwrap();
237 mint.mint_authority = COption::Some(owner_pubkey);
238 mint.supply = 42;
239 mint.decimals = 3;
240 mint.is_initialized = true;
241 mint.freeze_authority = COption::Some(owner_pubkey);
242 Mint::pack(mint, &mut mint_data).unwrap();
243
244 assert_eq!(
245 parse_token_v3(&mint_data, None).unwrap(),
246 TokenAccountType::Mint(UiMint {
247 mint_authority: Some(owner_pubkey.to_string()),
248 supply: 42.to_string(),
249 decimals: 3,
250 is_initialized: true,
251 freeze_authority: Some(owner_pubkey.to_string()),
252 extensions: vec![],
253 }),
254 );
255
256 let first_signer = Pubkey::new_from_array([1; 32]);
257 let second_signer = Pubkey::new_from_array([2; 32]);
258 let third_signer = Pubkey::new_from_array([3; 32]);
259 let mut multisig_data = vec![0; Multisig::get_packed_len()];
260 let mut signers = [Pubkey::default(); 11];
261 signers[0] = first_signer;
262 signers[1] = second_signer;
263 signers[2] = third_signer;
264 let mut multisig = Multisig::unpack_unchecked(&multisig_data).unwrap();
265 multisig.m = 2;
266 multisig.n = 3;
267 multisig.is_initialized = true;
268 multisig.signers = signers;
269 Multisig::pack(multisig, &mut multisig_data).unwrap();
270
271 assert_eq!(
272 parse_token_v3(&multisig_data, None).unwrap(),
273 TokenAccountType::Multisig(UiMultisig {
274 num_required_signers: 2,
275 num_valid_signers: 3,
276 is_initialized: true,
277 signers: vec![
278 first_signer.to_string(),
279 second_signer.to_string(),
280 third_signer.to_string()
281 ],
282 }),
283 );
284
285 let bad_data = vec![0; 4];
286 assert!(parse_token_v3(&bad_data, None).is_err());
287 }
288
289 #[test]
290 fn test_get_token_account_mint() {
291 let mint_pubkey = Pubkey::new_from_array([2; 32]);
292 let mut account_data = vec![0; Account::get_packed_len()];
293 let mut account = Account::unpack_unchecked(&account_data).unwrap();
294 account.mint = mint_pubkey;
295 account.state = AccountState::Initialized;
296 Account::pack(account, &mut account_data).unwrap();
297
298 let expected_mint_pubkey = Pubkey::from([2; 32]);
299 assert_eq!(
300 get_token_account_mint(&account_data),
301 Some(expected_mint_pubkey)
302 );
303 }
304
305 #[test]
306 fn test_ui_token_amount_real_string() {
307 assert_eq!(&real_number_string(1, 0), "1");
308 assert_eq!(&real_number_string_trimmed(1, 0), "1");
309 let token_amount =
310 token_amount_to_ui_amount_v3(1, &SplTokenAdditionalDataV2::with_decimals(0));
311 assert_eq!(
312 token_amount.ui_amount_string,
313 real_number_string_trimmed(1, 0)
314 );
315 assert_eq!(token_amount.ui_amount, Some(1.0));
316 assert_eq!(&real_number_string(10, 0), "10");
317 assert_eq!(&real_number_string_trimmed(10, 0), "10");
318 let token_amount =
319 token_amount_to_ui_amount_v3(10, &SplTokenAdditionalDataV2::with_decimals(0));
320 assert_eq!(
321 token_amount.ui_amount_string,
322 real_number_string_trimmed(10, 0)
323 );
324 assert_eq!(token_amount.ui_amount, Some(10.0));
325 assert_eq!(&real_number_string(1, 9), "0.000000001");
326 assert_eq!(&real_number_string_trimmed(1, 9), "0.000000001");
327 let token_amount =
328 token_amount_to_ui_amount_v3(1, &SplTokenAdditionalDataV2::with_decimals(9));
329 assert_eq!(
330 token_amount.ui_amount_string,
331 real_number_string_trimmed(1, 9)
332 );
333 assert_eq!(token_amount.ui_amount, Some(0.000_000_001));
334 assert_eq!(&real_number_string(1_000_000_000, 9), "1.000000000");
335 assert_eq!(&real_number_string_trimmed(1_000_000_000, 9), "1");
336 let token_amount = token_amount_to_ui_amount_v3(
337 1_000_000_000,
338 &SplTokenAdditionalDataV2::with_decimals(9),
339 );
340 assert_eq!(
341 token_amount.ui_amount_string,
342 real_number_string_trimmed(1_000_000_000, 9)
343 );
344 assert_eq!(token_amount.ui_amount, Some(1.0));
345 assert_eq!(&real_number_string(1_234_567_890, 3), "1234567.890");
346 assert_eq!(&real_number_string_trimmed(1_234_567_890, 3), "1234567.89");
347 let token_amount = token_amount_to_ui_amount_v3(
348 1_234_567_890,
349 &SplTokenAdditionalDataV2::with_decimals(3),
350 );
351 assert_eq!(
352 token_amount.ui_amount_string,
353 real_number_string_trimmed(1_234_567_890, 3)
354 );
355 assert_eq!(token_amount.ui_amount, Some(1_234_567.89));
356 assert_eq!(
357 &real_number_string(1_234_567_890, 25),
358 "0.0000000000000001234567890"
359 );
360 assert_eq!(
361 &real_number_string_trimmed(1_234_567_890, 25),
362 "0.000000000000000123456789"
363 );
364 let token_amount = token_amount_to_ui_amount_v3(
365 1_234_567_890,
366 &SplTokenAdditionalDataV2::with_decimals(20),
367 );
368 assert_eq!(
369 token_amount.ui_amount_string,
370 real_number_string_trimmed(1_234_567_890, 20)
371 );
372 assert_eq!(token_amount.ui_amount, None);
373 }
374
375 #[test]
376 fn test_ui_token_amount_with_interest() {
377 let config = InterestBearingConfig {
379 initialization_timestamp: 0.into(),
380 pre_update_average_rate: 500.into(),
381 last_update_timestamp: INT_SECONDS_PER_YEAR.into(),
382 current_rate: 500.into(),
383 ..Default::default()
384 };
385 let additional_data = SplTokenAdditionalDataV2 {
386 decimals: 18,
387 interest_bearing_config: Some((config, INT_SECONDS_PER_YEAR)),
388 ..Default::default()
389 };
390 const ONE: u64 = 1_000_000_000_000_000_000;
391 const TEN: u64 = 10_000_000_000_000_000_000;
392 let token_amount = token_amount_to_ui_amount_v3(ONE, &additional_data);
393 assert!(
394 token_amount
395 .ui_amount_string
396 .starts_with("1.051271096376024117")
397 );
398 assert!(
399 (token_amount.ui_amount.unwrap() - 1.051_271_096_376_024_1_f64).abs() < f64::EPSILON
400 );
401 let token_amount = token_amount_to_ui_amount_v3(TEN, &additional_data);
402 assert!(
403 token_amount
404 .ui_amount_string
405 .starts_with("10.512710963760241611")
406 );
407 assert!(
408 (token_amount.ui_amount.unwrap() - 10.512_710_963_760_242_f64).abs() < f64::EPSILON
409 );
410
411 let config = InterestBearingConfig {
413 initialization_timestamp: 0.into(),
414 pre_update_average_rate: 32767.into(),
415 last_update_timestamp: 0.into(),
416 current_rate: 32767.into(),
417 ..Default::default()
418 };
419 let additional_data = SplTokenAdditionalDataV2 {
420 decimals: 0,
421 interest_bearing_config: Some((config, INT_SECONDS_PER_YEAR * 1_000)),
422 ..Default::default()
423 };
424 let token_amount = token_amount_to_ui_amount_v3(u64::MAX, &additional_data);
425 assert_eq!(token_amount.ui_amount, Some(f64::INFINITY));
426 assert_eq!(token_amount.ui_amount_string, "inf");
427 }
428
429 #[test]
430 fn test_ui_token_amount_with_multiplier() {
431 let config = ScaledUiAmountConfig {
433 new_multiplier: 2f64.into(),
434 ..Default::default()
435 };
436 let additional_data = SplTokenAdditionalDataV2 {
437 decimals: 18,
438 scaled_ui_amount_config: Some((config, 0)),
439 ..Default::default()
440 };
441 const ONE: u64 = 1_000_000_000_000_000_000;
442 const TEN: u64 = 10_000_000_000_000_000_000;
443 let token_amount = token_amount_to_ui_amount_v3(ONE, &additional_data);
444 assert_eq!(token_amount.ui_amount_string, "2");
445 assert!(token_amount.ui_amount_string.starts_with('2'));
446 assert!((token_amount.ui_amount.unwrap() - 2.0).abs() < f64::EPSILON);
447 let token_amount = token_amount_to_ui_amount_v3(TEN, &additional_data);
448 assert!(token_amount.ui_amount_string.starts_with("20"));
449 assert!((token_amount.ui_amount.unwrap() - 20.0).abs() < f64::EPSILON);
450
451 let config = ScaledUiAmountConfig {
453 new_multiplier: f64::INFINITY.into(),
454 ..Default::default()
455 };
456 let additional_data = SplTokenAdditionalDataV2 {
457 decimals: 0,
458 scaled_ui_amount_config: Some((config, 0)),
459 ..Default::default()
460 };
461 let token_amount = token_amount_to_ui_amount_v3(u64::MAX, &additional_data);
462 assert_eq!(token_amount.ui_amount, Some(f64::INFINITY));
463 assert_eq!(token_amount.ui_amount_string, "inf");
464 }
465
466 #[test]
467 fn test_ui_token_amount_real_string_zero() {
468 assert_eq!(&real_number_string(0, 0), "0");
469 assert_eq!(&real_number_string_trimmed(0, 0), "0");
470 let token_amount =
471 token_amount_to_ui_amount_v3(0, &SplTokenAdditionalDataV2::with_decimals(0));
472 assert_eq!(
473 token_amount.ui_amount_string,
474 real_number_string_trimmed(0, 0)
475 );
476 assert_eq!(token_amount.ui_amount, Some(0.0));
477 assert_eq!(&real_number_string(0, 9), "0.000000000");
478 assert_eq!(&real_number_string_trimmed(0, 9), "0");
479 let token_amount =
480 token_amount_to_ui_amount_v3(0, &SplTokenAdditionalDataV2::with_decimals(9));
481 assert_eq!(
482 token_amount.ui_amount_string,
483 real_number_string_trimmed(0, 9)
484 );
485 assert_eq!(token_amount.ui_amount, Some(0.0));
486 assert_eq!(&real_number_string(0, 25), "0.0000000000000000000000000");
487 assert_eq!(&real_number_string_trimmed(0, 25), "0");
488 let token_amount =
489 token_amount_to_ui_amount_v3(0, &SplTokenAdditionalDataV2::with_decimals(20));
490 assert_eq!(
491 token_amount.ui_amount_string,
492 real_number_string_trimmed(0, 20)
493 );
494 assert_eq!(token_amount.ui_amount, None);
495 }
496
497 #[test]
498 fn test_parse_token_account_with_extensions() {
499 let mint_pubkey = Pubkey::new_from_array([2; 32]);
500 let owner_pubkey = Pubkey::new_from_array([3; 32]);
501
502 let account_base = Account {
503 mint: mint_pubkey,
504 owner: owner_pubkey,
505 amount: 42,
506 state: AccountState::Initialized,
507 is_native: COption::None,
508 close_authority: COption::Some(owner_pubkey),
509 delegate: COption::None,
510 delegated_amount: 0,
511 };
512 let account_size = ExtensionType::try_calculate_account_len::<Account>(&[
513 ExtensionType::ImmutableOwner,
514 ExtensionType::MemoTransfer,
515 ])
516 .unwrap();
517 let mut account_data = vec![0; account_size];
518 let mut account_state =
519 StateWithExtensionsMut::<Account>::unpack_uninitialized(&mut account_data).unwrap();
520
521 account_state.base = account_base;
522 account_state.pack_base();
523 account_state.init_account_type().unwrap();
524
525 assert!(parse_token_v3(&account_data, None).is_err());
526 assert_eq!(
527 parse_token_v3(
528 &account_data,
529 Some(&SplTokenAdditionalDataV2::with_decimals(2))
530 )
531 .unwrap(),
532 TokenAccountType::Account(UiTokenAccount {
533 mint: mint_pubkey.to_string(),
534 owner: owner_pubkey.to_string(),
535 token_amount: UiTokenAmount {
536 ui_amount: Some(0.42),
537 decimals: 2,
538 amount: "42".to_string(),
539 ui_amount_string: "0.42".to_string()
540 },
541 delegate: None,
542 state: UiAccountState::Initialized,
543 is_native: false,
544 rent_exempt_reserve: None,
545 delegated_amount: None,
546 close_authority: Some(owner_pubkey.to_string()),
547 extensions: vec![],
548 }),
549 );
550
551 let mut account_data = vec![0; account_size];
552 let mut account_state =
553 StateWithExtensionsMut::<Account>::unpack_uninitialized(&mut account_data).unwrap();
554
555 account_state.base = account_base;
556 account_state.pack_base();
557 account_state.init_account_type().unwrap();
558
559 account_state
560 .init_extension::<ImmutableOwner>(true)
561 .unwrap();
562 let memo_transfer = account_state.init_extension::<MemoTransfer>(true).unwrap();
563 memo_transfer.require_incoming_transfer_memos = true.into();
564
565 assert!(parse_token_v3(&account_data, None).is_err());
566 assert_eq!(
567 parse_token_v3(
568 &account_data,
569 Some(&SplTokenAdditionalDataV2::with_decimals(2))
570 )
571 .unwrap(),
572 TokenAccountType::Account(UiTokenAccount {
573 mint: mint_pubkey.to_string(),
574 owner: owner_pubkey.to_string(),
575 token_amount: UiTokenAmount {
576 ui_amount: Some(0.42),
577 decimals: 2,
578 amount: "42".to_string(),
579 ui_amount_string: "0.42".to_string()
580 },
581 delegate: None,
582 state: UiAccountState::Initialized,
583 is_native: false,
584 rent_exempt_reserve: None,
585 delegated_amount: None,
586 close_authority: Some(owner_pubkey.to_string()),
587 extensions: vec![
588 UiExtension::ImmutableOwner,
589 UiExtension::MemoTransfer(UiMemoTransfer {
590 require_incoming_transfer_memos: true,
591 }),
592 ],
593 }),
594 );
595 }
596
597 #[test]
598 fn test_parse_token_mint_with_extensions() {
599 let owner_pubkey = Pubkey::new_from_array([3; 32]);
600 let mint_size =
601 ExtensionType::try_calculate_account_len::<Mint>(&[ExtensionType::MintCloseAuthority])
602 .unwrap();
603 let mint_base = Mint {
604 mint_authority: COption::Some(owner_pubkey),
605 supply: 42,
606 decimals: 3,
607 is_initialized: true,
608 freeze_authority: COption::Some(owner_pubkey),
609 };
610 let mut mint_data = vec![0; mint_size];
611 let mut mint_state =
612 StateWithExtensionsMut::<Mint>::unpack_uninitialized(&mut mint_data).unwrap();
613
614 mint_state.base = mint_base;
615 mint_state.pack_base();
616 mint_state.init_account_type().unwrap();
617
618 assert_eq!(
619 parse_token_v3(&mint_data, None).unwrap(),
620 TokenAccountType::Mint(UiMint {
621 mint_authority: Some(owner_pubkey.to_string()),
622 supply: 42.to_string(),
623 decimals: 3,
624 is_initialized: true,
625 freeze_authority: Some(owner_pubkey.to_string()),
626 extensions: vec![],
627 }),
628 );
629
630 let mut mint_data = vec![0; mint_size];
631 let mut mint_state =
632 StateWithExtensionsMut::<Mint>::unpack_uninitialized(&mut mint_data).unwrap();
633
634 let mint_close_authority = mint_state
635 .init_extension::<MintCloseAuthority>(true)
636 .unwrap();
637 mint_close_authority.close_authority = Some(owner_pubkey).try_into().unwrap();
638 mint_state.base = mint_base;
639 mint_state.pack_base();
640 mint_state.init_account_type().unwrap();
641
642 assert_eq!(
643 parse_token_v3(&mint_data, None).unwrap(),
644 TokenAccountType::Mint(UiMint {
645 mint_authority: Some(owner_pubkey.to_string()),
646 supply: 42.to_string(),
647 decimals: 3,
648 is_initialized: true,
649 freeze_authority: Some(owner_pubkey.to_string()),
650 extensions: vec![UiExtension::MintCloseAuthority(UiMintCloseAuthority {
651 close_authority: Some(owner_pubkey.to_string()),
652 })],
653 }),
654 );
655 }
656}