1use super::interface::{TokenInterface, TokenState};
2use async_trait::async_trait;
3use solana_program::{program_pack::Pack, pubkey::Pubkey};
4use solana_sdk::instruction::Instruction;
5use spl_associated_token_account::{
6 get_associated_token_address_with_program_id, instruction::create_associated_token_account,
7};
8use spl_token_2022::{
9 extension::{
10 cpi_guard::CpiGuard, interest_bearing_mint::InterestBearingConfig,
11 transfer_fee::TransferFeeConfig, BaseStateWithExtensions, ExtensionType,
12 StateWithExtensions,
13 },
14 instruction,
15 state::{Account as Token2022AccountState, AccountState, Mint as Token2022MintState},
16};
17use std::fmt::Debug;
18#[derive(Debug)]
38pub struct Token2022Account {
39 pub mint: Pubkey,
40 pub owner: Pubkey,
41 pub amount: u64,
42 pub delegate: Option<Pubkey>,
43 pub state: u8,
44 pub is_native: Option<u64>,
45 pub delegated_amount: u64,
46 pub close_authority: Option<Pubkey>,
47 pub extension_data: Vec<u8>,
49}
50
51impl TokenState for Token2022Account {
52 fn mint(&self) -> Pubkey {
53 self.mint
54 }
55 fn owner(&self) -> Pubkey {
56 self.owner
57 }
58 fn amount(&self) -> u64 {
59 self.amount
60 }
61 fn decimals(&self) -> u8 {
62 0
63 }
64 fn as_any(&self) -> &dyn std::any::Any {
65 self
66 }
67}
68
69impl Token2022Account {
70 pub fn has_extension(&self, extension_type: ExtensionType) -> bool {
72 if let Ok(account_with_extensions) =
73 StateWithExtensions::<Token2022AccountState>::unpack(&self.extension_data)
74 {
75 account_with_extensions
76 .get_extension_types()
77 .unwrap_or_default()
78 .contains(&extension_type)
79 } else {
80 false
81 }
82 }
83
84 pub fn get_transfer_fee(&self) -> Option<TransferFeeConfig> {
86 if let Ok(account_with_extensions) =
87 StateWithExtensions::<Token2022AccountState>::unpack(&self.extension_data)
88 {
89 account_with_extensions.get_extension::<TransferFeeConfig>().ok().copied()
90 } else {
91 None
92 }
93 }
94
95 pub fn is_non_transferable(&self) -> bool {
97 self.has_extension(ExtensionType::NonTransferable)
98 }
99
100 pub fn get_interest_config(&self) -> Option<InterestBearingConfig> {
102 if let Ok(account_with_extensions) =
103 StateWithExtensions::<Token2022AccountState>::unpack(&self.extension_data)
104 {
105 account_with_extensions.get_extension::<InterestBearingConfig>().ok().copied()
106 } else {
107 None
108 }
109 }
110
111 pub fn is_cpi_guarded(&self) -> bool {
113 if let Ok(account_with_extensions) =
114 StateWithExtensions::<Token2022AccountState>::unpack(&self.extension_data)
115 {
116 if let Ok(cpi_guard) = account_with_extensions.get_extension::<CpiGuard>() {
117 return cpi_guard.lock_cpi.into();
118 }
119 }
120 false
121 }
122
123 pub fn has_confidential_transfers(&self) -> bool {
125 self.has_extension(ExtensionType::ConfidentialTransferAccount)
126 }
127}
128
129pub struct Token2022Program;
130
131impl Default for Token2022Program {
132 fn default() -> Self {
133 Self::new()
134 }
135}
136
137impl Token2022Program {
138 pub fn new() -> Self {
139 Self
140 }
141
142 fn get_program_id(&self) -> Pubkey {
143 spl_token_2022::id()
144 }
145}
146
147#[async_trait]
148impl TokenInterface for Token2022Program {
149 fn program_id(&self) -> Pubkey {
150 self.get_program_id()
151 }
152
153 fn unpack_token_account(
154 &self,
155 data: &[u8],
156 ) -> Result<Box<dyn TokenState + Send + Sync>, Box<dyn std::error::Error + Send + Sync>> {
157 let account = StateWithExtensions::<Token2022AccountState>::unpack(data)?;
158 let base = account.base;
159
160 Ok(Box::new(Token2022Account {
161 mint: base.mint,
162 owner: base.owner,
163 amount: base.amount,
164 delegate: base.delegate.into(),
165 state: match base.state {
166 AccountState::Uninitialized => 0,
167 AccountState::Initialized => 1,
168 AccountState::Frozen => 2,
169 },
170 is_native: base.is_native.into(),
171 delegated_amount: base.delegated_amount,
172 close_authority: base.close_authority.into(),
173 extension_data: data.to_vec(),
174 }))
175 }
176
177 fn create_initialize_account_instruction(
178 &self,
179 account: &Pubkey,
180 mint: &Pubkey,
181 owner: &Pubkey,
182 ) -> Result<Instruction, Box<dyn std::error::Error + Send + Sync>> {
183 Ok(spl_token_2022::instruction::initialize_account3(
184 &self.program_id(),
185 account,
186 mint,
187 owner,
188 )?)
189 }
190
191 fn create_transfer_instruction(
192 &self,
193 source: &Pubkey,
194 destination: &Pubkey,
195 authority: &Pubkey,
196 amount: u64,
197 ) -> Result<Instruction, Box<dyn std::error::Error + Send + Sync>> {
198 Ok(spl_token_2022::instruction::transfer(
200 &self.program_id(),
201 source,
202 destination,
203 authority,
204 &[],
205 amount,
206 )?)
207 }
208
209 fn create_transfer_checked_instruction(
210 &self,
211 source: &Pubkey,
212 mint: &Pubkey,
213 destination: &Pubkey,
214 authority: &Pubkey,
215 amount: u64,
216 decimals: u8,
217 ) -> Result<Instruction, Box<dyn std::error::Error + Send + Sync>> {
218 Ok(spl_token_2022::instruction::transfer_checked(
219 &self.program_id(),
220 source,
221 mint,
222 destination,
223 authority,
224 &[],
225 amount,
226 decimals,
227 )?)
228 }
229
230 fn get_associated_token_address(&self, wallet: &Pubkey, mint: &Pubkey) -> Pubkey {
231 get_associated_token_address_with_program_id(wallet, mint, &self.program_id())
232 }
233
234 fn create_associated_token_account_instruction(
235 &self,
236 funding_account: &Pubkey,
237 wallet: &Pubkey,
238 mint: &Pubkey,
239 ) -> Instruction {
240 create_associated_token_account(funding_account, wallet, mint, &self.program_id())
241 }
242
243 fn get_mint_decimals(
244 &self,
245 mint_data: &[u8],
246 ) -> Result<u8, Box<dyn std::error::Error + Send + Sync>> {
247 let mint = StateWithExtensions::<Token2022MintState>::unpack(mint_data)?;
248 Ok(mint.base.decimals)
249 }
250
251 fn decode_transfer_instruction(
252 &self,
253 data: &[u8],
254 ) -> Result<u64, Box<dyn std::error::Error + Send + Sync>> {
255 let instruction = instruction::TokenInstruction::unpack(data)?;
256 match instruction {
257 instruction::TokenInstruction::Transfer { amount } => Ok(amount),
258 instruction::TokenInstruction::TransferChecked { amount, .. } => Ok(amount),
259 _ => Err("Not a transfer instruction".into()),
260 }
261 }
262}