1use super::{
2 interface::{TokenInterface, TokenState},
3 TokenType,
4};
5use async_trait::async_trait;
6use solana_program::{program_pack::Pack, pubkey::Pubkey};
7use solana_sdk::instruction::Instruction;
8use spl_associated_token_account::{
9 get_associated_token_address_with_program_id, instruction::create_associated_token_account,
10};
11use spl_token::{
12 self,
13 state::{Account as TokenAccountState, AccountState, Mint as MintState},
14};
15
16#[derive(Debug)]
17pub struct TokenAccount {
18 pub mint: Pubkey,
19 pub owner: Pubkey,
20 pub amount: u64,
21 pub delegate: Option<Pubkey>,
22 pub state: u8,
23 pub is_native: Option<u64>,
24 pub delegated_amount: u64,
25 pub close_authority: Option<Pubkey>,
26}
27
28impl TokenState for TokenAccount {
29 fn mint(&self) -> Pubkey {
30 self.mint
31 }
32 fn owner(&self) -> Pubkey {
33 self.owner
34 }
35 fn amount(&self) -> u64 {
36 self.amount
37 }
38 fn decimals(&self) -> u8 {
39 0
40 }
41 fn as_any(&self) -> &dyn std::any::Any {
42 self
43 }
44}
45
46pub struct TokenProgram {
47 token_type: TokenType,
48}
49
50impl TokenProgram {
51 pub fn new(token_type: TokenType) -> Self {
52 Self { token_type }
53 }
54
55 fn get_program_id(&self) -> Pubkey {
56 match self.token_type {
57 TokenType::Spl => spl_token::id(),
58 TokenType::Token2022 => spl_token_2022::id(),
59 }
60 }
61}
62
63#[async_trait]
64impl TokenInterface for TokenProgram {
65 fn program_id(&self) -> Pubkey {
66 self.get_program_id()
67 }
68
69 fn unpack_token_account(
70 &self,
71 data: &[u8],
72 ) -> Result<Box<dyn TokenState + Send + Sync>, Box<dyn std::error::Error + Send + Sync>> {
73 let account = TokenAccountState::unpack(data)?;
74
75 Ok(Box::new(TokenAccount {
76 mint: account.mint,
77 owner: account.owner,
78 amount: account.amount,
79 delegate: account.delegate.into(),
80 state: match account.state {
81 AccountState::Uninitialized => 0,
82 AccountState::Initialized => 1,
83 AccountState::Frozen => 2,
84 },
85 is_native: account.is_native.into(),
86 delegated_amount: account.delegated_amount,
87 close_authority: account.close_authority.into(),
88 }))
89 }
90
91 fn create_initialize_account_instruction(
92 &self,
93 account: &Pubkey,
94 mint: &Pubkey,
95 owner: &Pubkey,
96 ) -> Result<Instruction, Box<dyn std::error::Error + Send + Sync>> {
97 Ok(spl_token::instruction::initialize_account(&self.program_id(), account, mint, owner)?)
98 }
99
100 fn create_transfer_instruction(
101 &self,
102 source: &Pubkey,
103 destination: &Pubkey,
104 authority: &Pubkey,
105 amount: u64,
106 ) -> Result<Instruction, Box<dyn std::error::Error + Send + Sync>> {
107 Ok(spl_token::instruction::transfer(
108 &self.program_id(),
109 source,
110 destination,
111 authority,
112 &[],
113 amount,
114 )?)
115 }
116
117 fn create_transfer_checked_instruction(
118 &self,
119 source: &Pubkey,
120 mint: &Pubkey,
121 destination: &Pubkey,
122 authority: &Pubkey,
123 amount: u64,
124 decimals: u8,
125 ) -> Result<Instruction, Box<dyn std::error::Error + Send + Sync>> {
126 Ok(spl_token::instruction::transfer_checked(
127 &self.program_id(),
128 source,
129 mint,
130 destination,
131 authority,
132 &[],
133 amount,
134 decimals,
135 )?)
136 }
137
138 fn get_associated_token_address(&self, wallet: &Pubkey, mint: &Pubkey) -> Pubkey {
139 get_associated_token_address_with_program_id(wallet, mint, &self.program_id())
140 }
141
142 fn create_associated_token_account_instruction(
143 &self,
144 funding_account: &Pubkey,
145 wallet: &Pubkey,
146 mint: &Pubkey,
147 ) -> Instruction {
148 create_associated_token_account(funding_account, wallet, mint, &self.program_id())
149 }
150
151 fn get_mint_decimals(
152 &self,
153 mint_data: &[u8],
154 ) -> Result<u8, Box<dyn std::error::Error + Send + Sync>> {
155 Ok(MintState::unpack(mint_data)?.decimals)
156 }
157
158 fn decode_transfer_instruction(
159 &self,
160 data: &[u8],
161 ) -> Result<u64, Box<dyn std::error::Error + Send + Sync>> {
162 let instruction = spl_token::instruction::TokenInstruction::unpack(data)?;
163 match instruction {
164 spl_token::instruction::TokenInstruction::Transfer { amount } => Ok(amount),
165 spl_token::instruction::TokenInstruction::TransferChecked { amount, .. } => Ok(amount),
166 _ => Err("Not a transfer instruction".into()),
167 }
168 }
169}