1use crate::token::{
2 interface::TokenMint,
3 spl_token_2022_util::{
4 try_parse_account_extension, try_parse_mint_extension, AccountExtension, MintExtension,
5 ParsedExtension,
6 },
7};
8
9use super::interface::{TokenInterface, TokenState};
10use async_trait::async_trait;
11use solana_program::{program_pack::Pack, pubkey::Pubkey};
12use solana_sdk::instruction::Instruction;
13use spl_associated_token_account_interface::{
14 address::get_associated_token_address_with_program_id,
15 instruction::create_associated_token_account,
16};
17use spl_token_2022_interface::{
18 extension::{transfer_fee::TransferFeeConfig, ExtensionType, StateWithExtensions},
19 state::{Account as Token2022AccountState, AccountState, Mint as Token2022MintState},
20};
21use std::{collections::HashMap, fmt::Debug};
22
23#[derive(Debug)]
24pub struct Token2022Account {
25 pub mint: Pubkey,
26 pub owner: Pubkey,
27 pub amount: u64,
28 pub delegate: Option<Pubkey>,
29 pub state: u8,
30 pub is_native: Option<u64>,
31 pub delegated_amount: u64,
32 pub close_authority: Option<Pubkey>,
33 pub extensions_types: Vec<ExtensionType>,
35 pub extensions: HashMap<u16, ParsedExtension>,
37}
38
39impl TokenState for Token2022Account {
40 fn mint(&self) -> Pubkey {
41 self.mint
42 }
43 fn owner(&self) -> Pubkey {
44 self.owner
45 }
46 fn amount(&self) -> u64 {
47 self.amount
48 }
49 fn decimals(&self) -> u8 {
50 0
51 }
52 fn as_any(&self) -> &dyn std::any::Any {
53 self
54 }
55}
56
57impl Token2022Account {
58 pub fn has_memo_extension(&self) -> bool {
62 self.has_extension(ExtensionType::MemoTransfer)
63 }
64
65 pub fn has_immutable_owner_extension(&self) -> bool {
66 self.has_extension(ExtensionType::ImmutableOwner)
67 }
68
69 pub fn has_default_account_state_extension(&self) -> bool {
70 self.has_extension(ExtensionType::DefaultAccountState)
71 }
72}
73
74impl Token2022Extensions for Token2022Account {
75 fn get_extensions(&self) -> &HashMap<u16, ParsedExtension> {
76 &self.extensions
77 }
78
79 fn get_extension_types(&self) -> &Vec<ExtensionType> {
80 &self.extensions_types
81 }
82
83 fn has_confidential_transfer_extension(&self) -> bool {
88 self.has_extension(ExtensionType::ConfidentialTransferAccount)
89 }
90
91 fn has_transfer_hook_extension(&self) -> bool {
92 self.has_extension(ExtensionType::TransferHookAccount)
93 }
94
95 fn has_pausable_extension(&self) -> bool {
96 self.has_extension(ExtensionType::PausableAccount)
97 }
98
99 fn is_non_transferable(&self) -> bool {
100 self.has_extension(ExtensionType::NonTransferableAccount)
101 }
102}
103
104#[derive(Debug)]
105pub struct Token2022Mint {
106 pub mint: Pubkey,
107 pub mint_authority: Option<Pubkey>,
108 pub supply: u64,
109 pub decimals: u8,
110 pub is_initialized: bool,
111 pub freeze_authority: Option<Pubkey>,
112 pub extensions_types: Vec<ExtensionType>,
114 pub extensions: HashMap<u16, ParsedExtension>,
116}
117
118impl TokenMint for Token2022Mint {
119 fn address(&self) -> Pubkey {
120 self.mint
121 }
122
123 fn decimals(&self) -> u8 {
124 self.decimals
125 }
126
127 fn mint_authority(&self) -> Option<Pubkey> {
128 self.mint_authority
129 }
130
131 fn supply(&self) -> u64 {
132 self.supply
133 }
134
135 fn freeze_authority(&self) -> Option<Pubkey> {
136 self.freeze_authority
137 }
138
139 fn is_initialized(&self) -> bool {
140 self.is_initialized
141 }
142
143 fn get_token_program(&self) -> Box<dyn TokenInterface> {
144 Box::new(Token2022Program::new())
145 }
146
147 fn as_any(&self) -> &dyn std::any::Any {
148 self
149 }
150}
151
152impl Token2022Mint {
153 fn get_transfer_fee(&self) -> Option<TransferFeeConfig> {
154 match self.get_extension(ExtensionType::TransferFeeConfig) {
155 Some(ParsedExtension::Mint(MintExtension::TransferFeeConfig(config))) => Some(*config),
156 _ => None,
157 }
158 }
159
160 pub fn calculate_transfer_fee(
163 &self,
164 amount: u64,
165 current_epoch: u64,
166 ) -> Result<Option<u64>, crate::error::KoraError> {
167 if let Some(fee_config) = self.get_transfer_fee() {
168 let transfer_fee = if current_epoch >= u64::from(fee_config.newer_transfer_fee.epoch) {
169 &fee_config.newer_transfer_fee
170 } else {
171 &fee_config.older_transfer_fee
172 };
173
174 let basis_points = u16::from(transfer_fee.transfer_fee_basis_points);
175 let maximum_fee = u64::from(transfer_fee.maximum_fee);
176
177 let fee_amount = (amount as u128)
178 .checked_mul(basis_points as u128)
179 .and_then(|product| product.checked_add(10_000 - 1))
180 .and_then(|product| product.checked_div(10_000))
181 .and_then(
182 |result| if result <= u64::MAX as u128 { Some(result as u64) } else { None },
183 )
184 .ok_or_else(|| {
185 log::error!(
186 "Transfer fee calculation overflow: amount={}, basis_points={}",
187 amount,
188 basis_points
189 );
190 crate::error::KoraError::ValidationError(format!(
191 "Transfer fee calculation overflow: amount={}, basis_points={}",
192 amount, basis_points
193 ))
194 })?;
195 Ok(Some(std::cmp::min(fee_amount, maximum_fee)))
196 } else {
197 Ok(None)
198 }
199 }
200
201 pub fn has_confidential_mint_burn_extension(&self) -> bool {
202 self.has_extension(ExtensionType::ConfidentialMintBurn)
203 }
204
205 pub fn has_mint_close_authority_extension(&self) -> bool {
206 self.has_extension(ExtensionType::MintCloseAuthority)
207 }
208
209 pub fn has_interest_bearing_extension(&self) -> bool {
210 self.has_extension(ExtensionType::InterestBearingConfig)
211 }
212
213 pub fn has_permanent_delegate_extension(&self) -> bool {
214 self.has_extension(ExtensionType::PermanentDelegate)
215 }
216}
217
218impl Token2022Extensions for Token2022Mint {
219 fn get_extensions(&self) -> &HashMap<u16, ParsedExtension> {
220 &self.extensions
221 }
222
223 fn get_extension_types(&self) -> &Vec<ExtensionType> {
224 &self.extensions_types
225 }
226
227 fn has_confidential_transfer_extension(&self) -> bool {
232 self.has_extension(ExtensionType::ConfidentialTransferMint)
233 }
234
235 fn has_transfer_hook_extension(&self) -> bool {
236 self.has_extension(ExtensionType::TransferHook)
237 }
238
239 fn has_pausable_extension(&self) -> bool {
240 self.has_extension(ExtensionType::Pausable)
241 }
242
243 fn is_non_transferable(&self) -> bool {
244 self.has_extension(ExtensionType::NonTransferable)
245 }
246}
247
248pub struct Token2022Program;
249
250impl Token2022Program {
251 pub fn new() -> Self {
252 Self
253 }
254}
255
256impl Default for Token2022Program {
257 fn default() -> Self {
258 Self::new()
259 }
260}
261
262#[async_trait]
263impl TokenInterface for Token2022Program {
264 fn program_id(&self) -> Pubkey {
265 spl_token_2022_interface::id()
266 }
267
268 fn unpack_token_account(
269 &self,
270 data: &[u8],
271 ) -> Result<Box<dyn TokenState + Send + Sync>, Box<dyn std::error::Error + Send + Sync>> {
272 let account = StateWithExtensions::<Token2022AccountState>::unpack(data)?;
273 let base = account.base;
274
275 let mut extensions = HashMap::new();
277 let mut extensions_types = Vec::new();
278
279 if data.len() > Token2022AccountState::LEN {
280 for &extension_type in AccountExtension::EXTENSIONS {
281 if let Some(parsed_ext) = try_parse_account_extension(&account, extension_type) {
282 extensions.insert(extension_type as u16, parsed_ext);
283 extensions_types.push(extension_type);
284 }
285 }
286 }
287
288 Ok(Box::new(Token2022Account {
289 mint: base.mint,
290 owner: base.owner,
291 amount: base.amount,
292 delegate: base.delegate.into(),
293 state: match base.state {
294 AccountState::Uninitialized => 0,
295 AccountState::Initialized => 1,
296 AccountState::Frozen => 2,
297 },
298 is_native: base.is_native.into(),
299 delegated_amount: base.delegated_amount,
300 close_authority: base.close_authority.into(),
301 extensions_types,
302 extensions,
303 }))
304 }
305
306 fn create_initialize_account_instruction(
307 &self,
308 account: &Pubkey,
309 mint: &Pubkey,
310 owner: &Pubkey,
311 ) -> Result<Instruction, Box<dyn std::error::Error + Send + Sync>> {
312 Ok(spl_token_2022_interface::instruction::initialize_account3(
313 &self.program_id(),
314 account,
315 mint,
316 owner,
317 )?)
318 }
319
320 fn create_transfer_instruction(
321 &self,
322 source: &Pubkey,
323 destination: &Pubkey,
324 authority: &Pubkey,
325 amount: u64,
326 ) -> Result<Instruction, Box<dyn std::error::Error + Send + Sync>> {
327 #[allow(deprecated)]
329 Ok(spl_token_2022_interface::instruction::transfer(
330 &self.program_id(),
331 source,
332 destination,
333 authority,
334 &[],
335 amount,
336 )?)
337 }
338
339 fn create_transfer_checked_instruction(
340 &self,
341 source: &Pubkey,
342 mint: &Pubkey,
343 destination: &Pubkey,
344 authority: &Pubkey,
345 amount: u64,
346 decimals: u8,
347 ) -> Result<Instruction, Box<dyn std::error::Error + Send + Sync>> {
348 Ok(spl_token_2022_interface::instruction::transfer_checked(
349 &self.program_id(),
350 source,
351 mint,
352 destination,
353 authority,
354 &[],
355 amount,
356 decimals,
357 )?)
358 }
359
360 fn get_associated_token_address(&self, wallet: &Pubkey, mint: &Pubkey) -> Pubkey {
361 get_associated_token_address_with_program_id(wallet, mint, &self.program_id())
362 }
363
364 fn create_associated_token_account_instruction(
365 &self,
366 funding_account: &Pubkey,
367 wallet: &Pubkey,
368 mint: &Pubkey,
369 ) -> Instruction {
370 create_associated_token_account(funding_account, wallet, mint, &self.program_id())
371 }
372
373 fn unpack_mint(
374 &self,
375 mint: &Pubkey,
376 mint_data: &[u8],
377 ) -> Result<Box<dyn TokenMint + Send + Sync>, Box<dyn std::error::Error + Send + Sync>> {
378 let mint_with_extensions = StateWithExtensions::<Token2022MintState>::unpack(mint_data)?;
379 let base = mint_with_extensions.base;
380
381 let mut extensions = HashMap::new();
383 let mut extensions_types = Vec::new();
384
385 if mint_data.len() > Token2022MintState::LEN {
386 for &extension_type in MintExtension::EXTENSIONS {
387 if let Some(parsed_ext) =
388 try_parse_mint_extension(&mint_with_extensions, extension_type)
389 {
390 extensions.insert(extension_type as u16, parsed_ext);
391 extensions_types.push(extension_type);
392 }
393 }
394 }
395
396 Ok(Box::new(Token2022Mint {
397 mint: *mint,
398 mint_authority: base.mint_authority.into(),
399 supply: base.supply,
400 decimals: base.decimals,
401 is_initialized: base.is_initialized,
402 freeze_authority: base.freeze_authority.into(),
403 extensions_types,
404 extensions,
405 }))
406 }
407}
408
409pub trait Token2022Extensions {
411 fn get_extensions(&self) -> &HashMap<u16, ParsedExtension>;
413
414 fn get_extension_types(&self) -> &Vec<ExtensionType>;
416
417 fn extension_key(ext_type: ExtensionType) -> u16 {
419 ext_type as u16
420 }
421
422 fn has_extension(&self, extension_type: ExtensionType) -> bool {
424 self.get_extension_types().contains(&extension_type)
425 }
426
427 fn get_extension(&self, extension_type: ExtensionType) -> Option<&ParsedExtension> {
429 self.get_extensions().get(&Self::extension_key(extension_type))
430 }
431
432 fn has_confidential_transfer_extension(&self) -> bool;
433
434 fn has_transfer_hook_extension(&self) -> bool;
435
436 fn has_pausable_extension(&self) -> bool;
437
438 fn is_non_transferable(&self) -> bool;
440}
441
442#[cfg(test)]
443mod tests {
444 use crate::tests::common::{
445 create_transfer_fee_config, MintAccountMockBuilder, TokenAccountMockBuilder,
446 };
447
448 use super::*;
449 use solana_sdk::pubkey::Pubkey;
450 use spl_pod::{
451 optional_keys::OptionalNonZeroPubkey,
452 primitives::{PodU16, PodU64},
453 };
454 use spl_token_2022_interface::extension::{
455 transfer_fee::{TransferFee, TransferFeeConfig},
456 ExtensionType,
457 };
458
459 pub fn create_test_extensions() -> HashMap<u16, ParsedExtension> {
460 let mut extensions = HashMap::new();
461 extensions.insert(
462 ExtensionType::TransferFeeConfig as u16,
463 ParsedExtension::Mint(MintExtension::TransferFeeConfig(create_transfer_fee_config(
464 100, 1000,
465 ))),
466 );
467 extensions
468 }
469
470 #[test]
471 fn test_token_program_token2022() {
472 let program = Token2022Program::new();
473 assert_eq!(program.program_id(), spl_token_2022_interface::id());
474 }
475
476 #[test]
477 fn test_token2022_program_creation() {
478 let program = Token2022Program::new();
479 assert_eq!(program.program_id(), spl_token_2022_interface::id());
480 }
481
482 #[test]
483 fn test_token2022_account_state() {
484 let mint = Pubkey::new_unique();
485 let owner = Pubkey::new_unique();
486 let amount = 1000;
487
488 let account = Token2022Account {
490 mint,
491 owner,
492 amount,
493 delegate: None,
494 state: 1, is_native: None,
496 delegated_amount: 0,
497 close_authority: None,
498 extensions_types: vec![ExtensionType::TransferFeeConfig],
499 extensions: create_test_extensions(),
500 };
501
502 assert_eq!(account.mint(), mint);
504 assert_eq!(account.owner(), owner);
505 assert_eq!(account.amount(), amount);
506
507 assert!(!account.extensions.is_empty());
509 }
510
511 #[test]
512 fn test_token2022_transfer_instruction() {
513 let source = Pubkey::new_unique();
514 let dest = Pubkey::new_unique();
515 let authority = Pubkey::new_unique();
516 let amount = 100;
517
518 let program = Token2022Program::new();
520 let ix = program.create_transfer_instruction(&source, &dest, &authority, amount).unwrap();
521
522 assert_eq!(ix.program_id, spl_token_2022_interface::id());
523 assert_eq!(ix.accounts[0].pubkey, source);
525 assert_eq!(ix.accounts[1].pubkey, dest);
526 assert_eq!(ix.accounts[2].pubkey, authority);
527 }
528
529 #[test]
530 fn test_token2022_transfer_checked_instruction() {
531 let source = Pubkey::new_unique();
532 let dest = Pubkey::new_unique();
533 let mint = Pubkey::new_unique();
534 let authority = Pubkey::new_unique();
535 let amount = 100;
536 let decimals = 9;
537
538 let program = Token2022Program::new();
539 let ix = program
540 .create_transfer_checked_instruction(
541 &source, &mint, &dest, &authority, amount, decimals,
542 )
543 .unwrap();
544
545 assert_eq!(ix.program_id, spl_token_2022_interface::id());
546 assert_eq!(ix.accounts[0].pubkey, source);
548 assert_eq!(ix.accounts[1].pubkey, mint);
549 assert_eq!(ix.accounts[2].pubkey, dest);
550 assert_eq!(ix.accounts[3].pubkey, authority);
551 }
552
553 #[test]
554 fn test_token2022_ata_derivation() {
555 let program = Token2022Program::new();
556 let wallet = Pubkey::new_unique();
557 let mint = Pubkey::new_unique();
558
559 let ata = program.get_associated_token_address(&wallet, &mint);
560
561 let expected_ata =
563 spl_associated_token_account_interface::address::get_associated_token_address_with_program_id(
564 &wallet,
565 &mint,
566 &spl_token_2022_interface::id(),
567 );
568 assert_eq!(ata, expected_ata);
569 }
570
571 #[test]
572 fn test_token2022_account_state_extensions() {
573 let owner = Pubkey::new_unique();
574 let mint = Pubkey::new_unique();
575 let amount = 1000;
576
577 let token_account = TokenAccountMockBuilder::new()
578 .with_mint(&mint)
579 .with_owner(&owner)
580 .with_amount(amount)
581 .build_as_custom_token2022_token_account(HashMap::new());
582
583 assert!(!token_account.has_extension(ExtensionType::TransferFeeConfig));
585 assert!(!token_account.has_extension(ExtensionType::NonTransferableAccount));
586 assert!(!token_account.has_extension(ExtensionType::CpiGuard));
587 }
588
589 #[test]
590 fn test_token2022_extension_support() {
591 let mint = Pubkey::new_unique();
592 let owner = Pubkey::new_unique();
593 let amount = 1000;
594
595 let token_account = TokenAccountMockBuilder::new()
596 .with_mint(&mint)
597 .with_owner(&owner)
598 .with_amount(amount)
599 .build_as_custom_token2022_token_account(create_test_extensions());
600
601 assert_eq!(token_account.mint(), mint);
602 assert_eq!(token_account.owner(), owner);
603 assert_eq!(token_account.amount(), amount);
604
605 assert!(!token_account.extensions.is_empty());
606 }
607
608 #[test]
609 fn test_token2022_mint_transfer_fee_edge_cases() {
610 let mint_pubkey = Pubkey::new_unique();
611
612 let mint = MintAccountMockBuilder::new()
613 .build_as_custom_token2022_mint(mint_pubkey, HashMap::new());
614
615 let fee = mint.calculate_transfer_fee(1000, 0).unwrap();
616 assert!(fee.is_none(), "Mint without transfer fee config should return None");
617
618 let mint = MintAccountMockBuilder::new()
619 .build_as_custom_token2022_mint(mint_pubkey, create_test_extensions());
620
621 let zero_fee = mint.calculate_transfer_fee(0, 0).unwrap();
623 assert!(zero_fee.is_some());
624 assert_eq!(zero_fee.unwrap(), 0, "Zero amount should result in zero fee");
625
626 let large_amount_fee = mint.calculate_transfer_fee(1_000_000, 0).unwrap();
628 assert!(large_amount_fee.is_some());
629 assert_eq!(large_amount_fee.unwrap(), 1000, "Large amount should be capped at maximum fee");
630 }
631
632 #[test]
633 fn test_token2022_mint_specific_extensions() {
634 let mint_pubkey = Pubkey::new_unique();
635 let mint = Token2022Mint {
636 mint: mint_pubkey,
637 mint_authority: None,
638 supply: 0,
639 decimals: 6,
640 is_initialized: true,
641 freeze_authority: None,
642 extensions_types: vec![
643 ExtensionType::InterestBearingConfig,
644 ExtensionType::PermanentDelegate,
645 ExtensionType::MintCloseAuthority,
646 ],
647 extensions: HashMap::new(), };
649
650 assert!(mint.has_interest_bearing_extension());
651 assert!(mint.has_permanent_delegate_extension());
652 assert!(mint.has_mint_close_authority_extension());
653 assert!(!mint.has_confidential_mint_burn_extension());
654 }
655
656 #[test]
657 fn test_token2022_account_extension_methods() {
658 let account = TokenAccountMockBuilder::new()
659 .with_extensions(vec![
660 ExtensionType::MemoTransfer,
661 ExtensionType::ImmutableOwner,
662 ExtensionType::DefaultAccountState,
663 ExtensionType::ConfidentialTransferAccount,
664 ExtensionType::TransferHookAccount,
665 ExtensionType::PausableAccount,
666 ])
667 .build_as_custom_token2022_token_account(HashMap::new());
668
669 assert!(account.has_memo_extension());
671 assert!(account.has_immutable_owner_extension());
672 assert!(account.has_default_account_state_extension());
673 assert!(account.has_confidential_transfer_extension());
674 assert!(account.has_transfer_hook_extension());
675 assert!(account.has_pausable_extension());
676
677 assert!(!account.is_non_transferable());
679 }
680
681 #[test]
682 fn test_token2022_mint_transfer_fee_calculation_with_fee() {
683 let mint_pubkey = Pubkey::new_unique();
684 let mut extensions = HashMap::new();
685 extensions.insert(
686 ExtensionType::TransferFeeConfig as u16,
687 ParsedExtension::Mint(MintExtension::TransferFeeConfig(
688 crate::tests::account_mock::create_transfer_fee_config(250, 1000),
689 )), );
691
692 let mint = MintAccountMockBuilder::new()
693 .with_extensions(vec![ExtensionType::TransferFeeConfig])
694 .build_as_custom_token2022_mint(mint_pubkey, extensions);
695
696 let test_cases: Vec<(u64, u64)> = vec![
698 (10000, 250), (100000, 1000), (1000, 25), (101, 3), (0, 0), ];
704
705 for (amount, expected_fee) in test_cases {
706 let fee = mint.calculate_transfer_fee(amount, 0).unwrap().unwrap_or(0);
707 assert_eq!(
708 fee, expected_fee,
709 "Fee mismatch for amount={amount}: got={fee}, expected={expected_fee}"
710 );
711 }
712 }
713
714 #[test]
715 fn test_token2022_mint_transfer_fee_epoch_handling() {
716 let mint_pubkey = Pubkey::new_unique();
717
718 let transfer_fee_config = TransferFeeConfig {
720 transfer_fee_config_authority: OptionalNonZeroPubkey::try_from(Some(
721 spl_pod::solana_pubkey::Pubkey::new_unique(),
722 ))
723 .unwrap(),
724 withdraw_withheld_authority: OptionalNonZeroPubkey::try_from(Some(
725 spl_pod::solana_pubkey::Pubkey::new_unique(),
726 ))
727 .unwrap(),
728 withheld_amount: PodU64::from(0),
729 older_transfer_fee: TransferFee {
730 epoch: PodU64::from(0),
731 transfer_fee_basis_points: PodU16::from(100), maximum_fee: PodU64::from(500),
733 },
734 newer_transfer_fee: TransferFee {
735 epoch: PodU64::from(10),
736 transfer_fee_basis_points: PodU16::from(200), maximum_fee: PodU64::from(1000),
738 },
739 };
740
741 let mut extensions = HashMap::new();
742 extensions.insert(
743 ExtensionType::TransferFeeConfig as u16,
744 ParsedExtension::Mint(MintExtension::TransferFeeConfig(transfer_fee_config)),
745 );
746
747 let mint = MintAccountMockBuilder::new()
748 .with_extensions(vec![ExtensionType::TransferFeeConfig])
749 .build_as_custom_token2022_mint(mint_pubkey, extensions);
750
751 let fee_old = mint.calculate_transfer_fee(10000, 5).unwrap().unwrap();
753 assert_eq!(fee_old, 100); let fee_new = mint.calculate_transfer_fee(10000, 15).unwrap().unwrap();
757 assert_eq!(fee_new, 200); }
759
760 #[test]
761 fn test_token2022_mint_all_extension_methods() {
762 let mint = MintAccountMockBuilder::new()
763 .with_extensions(vec![
764 ExtensionType::InterestBearingConfig,
765 ExtensionType::PermanentDelegate,
766 ExtensionType::MintCloseAuthority,
767 ExtensionType::ConfidentialMintBurn,
768 ExtensionType::ConfidentialTransferMint,
769 ExtensionType::TransferHook,
770 ExtensionType::Pausable,
771 ])
772 .build_as_custom_token2022_mint(Pubkey::new_unique(), HashMap::new());
773
774 assert!(mint.has_interest_bearing_extension());
776 assert!(mint.has_permanent_delegate_extension());
777 assert!(mint.has_mint_close_authority_extension());
778 assert!(mint.has_confidential_mint_burn_extension());
779 assert!(mint.has_confidential_transfer_extension());
780 assert!(mint.has_transfer_hook_extension());
781 assert!(mint.has_pausable_extension());
782
783 assert!(!mint.is_non_transferable());
785 }
786}