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