tensor_toolbox/token_2022/
mod.rs1pub mod extension;
2pub mod token;
3pub mod transfer;
4pub mod wns;
5
6use anchor_lang::{
7 solana_program::{account_info::AccountInfo, msg, program_error::ProgramError, pubkey::Pubkey},
8 Result,
9};
10use anchor_spl::{
11 token_2022::spl_token_2022::extension::transfer_hook::TransferHook,
12 token_interface::spl_token_2022::{
13 extension::{BaseStateWithExtensions, StateWithExtensions},
14 state::Mint,
15 },
16};
17use spl_token_metadata_interface::state::TokenMetadata;
18use std::str::FromStr;
19
20use self::extension::{get_extension, get_variable_len_extension};
21
22const LIBREPLEX_RO: &str = "_ro_";
24
25const LIBREPLEX_TRANSFER_HOOK: Pubkey = Pubkey::new_from_array([
27 171, 164, 26, 246, 200, 121, 33, 135, 216, 50, 55, 114, 165, 1, 182, 24, 180, 164, 102, 111, 3,
28 53, 2, 250, 50, 121, 61, 15, 194, 104, 5, 76,
29]);
30
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct RoyaltyInfo {
33 pub seller_fee: u16,
35
36 pub creators: Vec<(Pubkey, u8)>,
38}
39
40pub fn validate_mint(mint_info: &AccountInfo) -> Result<Option<RoyaltyInfo>> {
50 let mint_data = &mint_info.data.borrow();
51 let mint = StateWithExtensions::<Mint>::unpack(mint_data)?;
52
53 if !mint.base.is_initialized {
54 msg!("Mint is not initialized");
55 return Err(ProgramError::UninitializedAccount.into());
56 }
57
58 if mint.base.decimals != 0 {
59 msg!("Mint decimals must be 0");
60 return Err(ProgramError::InvalidAccountData.into());
61 }
62
63 if mint.base.supply != 1 {
64 msg!("Mint supply must be 1");
65 return Err(ProgramError::InvalidAccountData.into());
66 }
67
68 if mint.base.mint_authority.is_some() {
69 msg!("Mint authority must be none");
70 return Err(ProgramError::InvalidAccountData.into());
71 }
72
73 let hook_program: Option<Pubkey> =
74 if let Ok(extension) = get_extension::<TransferHook>(mint.get_tlv_data()) {
75 extension.program_id.into()
76 } else {
77 None
78 };
79
80 if hook_program == Some(LIBREPLEX_TRANSFER_HOOK) {
85 if let Ok(metadata) = get_variable_len_extension::<TokenMetadata>(mint.get_tlv_data()) {
86 let royalties = metadata
87 .additional_metadata
88 .iter()
89 .find(|(key, _)| key.starts_with(LIBREPLEX_RO));
90
91 if let Some((destination, seller_fee)) = royalties {
92 let seller_fee: u16 = seller_fee.parse().map_err(|_error| {
93 msg!("[ERROR] Could not parse seller fee");
94 ProgramError::InvalidAccountData
95 })?;
96
97 if seller_fee > 10000 {
98 msg!("[ERROR] Seller fee must be less than or equal to 10000");
99 return Err(ProgramError::InvalidAccountData.into());
100 }
101
102 let destination = Pubkey::from_str(destination.trim_start_matches(LIBREPLEX_RO))
103 .map_err(|_error| {
104 msg!("[ERROR] Could not parse destination address");
105 ProgramError::InvalidAccountData
106 })?;
107
108 return Ok(Some(RoyaltyInfo {
109 seller_fee,
110 creators: vec![(destination, 100)],
111 }));
112 }
113 }
114 }
115
116 Ok(None)
117}