Skip to main content

token_acl_client/
lib.rs

1mod generated;
2mod metadata;
3use std::future::Future;
4
5pub use generated::*;
6pub use metadata::*;
7
8#[cfg(feature = "fetch")]
9use solana_client::nonblocking;
10use solana_instruction::Instruction;
11use solana_program_error::ProgramError;
12use solana_program_option::COption;
13use solana_program_pack::Pack;
14use solana_pubkey::Pubkey;
15use spl_associated_token_account_interface::address::get_associated_token_address_with_program_id;
16use spl_associated_token_account_interface::instruction::{
17    create_associated_token_account, create_associated_token_account_idempotent,
18};
19pub use spl_tlv_account_resolution::state::{AccountDataResult, AccountFetchError};
20use spl_token_2022_interface::state::{Account, AccountState};
21use spl_token_2022_interface::ID as SPL_TOKEN_2022_ID;
22
23use crate::generated::errors::token_acl::TokenAclError;
24
25#[allow(clippy::too_many_arguments)]
26pub async fn create_thaw_permissionless_instruction_with_extra_metas<F, Fut>(
27    signer_pubkey: &Pubkey,
28    token_account_pubkey: &Pubkey,
29    mint_pubkey: &Pubkey,
30    mint_config_pubkey: &Pubkey,
31    token_program_pubkey: &Pubkey,
32    token_account_owner_pubkey: &Pubkey,
33    idempotent: bool,
34    fetch_account_data_fn: F,
35) -> Result<Instruction, AccountFetchError>
36where
37    F: Fn(Pubkey) -> Fut,
38    Fut: Future<Output = AccountDataResult>,
39{
40    let mint_config = fetch_account_data_fn(*mint_config_pubkey)
41        .await?
42        .and_then(|data| crate::accounts::MintConfig::from_bytes(&data).ok())
43        .ok_or(ProgramError::InvalidAccountData)?;
44
45    let flag_account = crate::accounts::FlagAccount::find_pda(token_account_pubkey).0;
46
47    if !mint_config.enable_permissionless_thaw {
48        return Err(TokenAclError::PermissionlessThawNotEnabled.into());
49    }
50
51    let mut ix = if idempotent {
52        crate::instructions::ThawPermissionlessIdempotentBuilder::new()
53            .gating_program(mint_config.gating_program)
54            .authority(*signer_pubkey)
55            .mint(*mint_pubkey)
56            .token_account(*token_account_pubkey)
57            .token_account_owner(*token_account_owner_pubkey)
58            .mint_config(*mint_config_pubkey)
59            .token_program(*token_program_pubkey)
60            .flag_account(flag_account)
61            .system_program(solana_system_interface::program::ID)
62            .instruction()
63    } else {
64        crate::instructions::ThawPermissionlessBuilder::new()
65            .gating_program(mint_config.gating_program)
66            .authority(*signer_pubkey)
67            .mint(*mint_pubkey)
68            .token_account(*token_account_pubkey)
69            .token_account_owner(*token_account_owner_pubkey)
70            .mint_config(*mint_config_pubkey)
71            .token_program(*token_program_pubkey)
72            .flag_account(flag_account)
73            .system_program(solana_system_interface::program::ID)
74            .instruction()
75    };
76
77    if mint_config.gating_program != Pubkey::default() {
78        token_acl_interface::offchain::add_extra_account_metas_for_thaw(
79            &mut ix,
80            &mint_config.gating_program,
81            signer_pubkey,
82            token_account_pubkey,
83            mint_pubkey,
84            token_account_owner_pubkey,
85            &flag_account,
86            fetch_account_data_fn,
87        )
88        .await?;
89    }
90
91    Ok(ix)
92}
93
94#[allow(clippy::too_many_arguments)]
95pub async fn create_freeze_permissionless_instruction_with_extra_metas<F, Fut>(
96    signer_pubkey: &Pubkey,
97    token_account_pubkey: &Pubkey,
98    mint_pubkey: &Pubkey,
99    mint_config_pubkey: &Pubkey,
100    token_program_pubkey: &Pubkey,
101    token_account_owner_pubkey: &Pubkey,
102    idempotent: bool,
103    fetch_account_data_fn: F,
104) -> Result<Instruction, AccountFetchError>
105where
106    F: Fn(Pubkey) -> Fut,
107    Fut: Future<Output = AccountDataResult>,
108{
109    let mint_config = fetch_account_data_fn(*mint_config_pubkey)
110        .await?
111        .and_then(|data| crate::accounts::MintConfig::from_bytes(&data).ok())
112        .ok_or(ProgramError::InvalidAccountData)?;
113
114    if !mint_config.enable_permissionless_freeze {
115        return Err(TokenAclError::PermissionlessFreezeNotEnabled.into());
116    }
117
118    let flag_account = crate::accounts::FlagAccount::find_pda(&token_account_pubkey).0;
119
120    let mut ix = if idempotent {
121        crate::instructions::FreezePermissionlessIdempotentBuilder::new()
122            .gating_program(mint_config.gating_program)
123            .authority(*signer_pubkey)
124            .mint(*mint_pubkey)
125            .token_account(*token_account_pubkey)
126            .token_account_owner(*token_account_owner_pubkey)
127            .mint_config(*mint_config_pubkey)
128            .token_program(*token_program_pubkey)
129            .system_program(solana_system_interface::program::ID)
130            .flag_account(flag_account)
131            .instruction()
132    } else {
133        crate::instructions::FreezePermissionlessBuilder::new()
134            .gating_program(mint_config.gating_program)
135            .authority(*signer_pubkey)
136            .mint(*mint_pubkey)
137            .token_account(*token_account_pubkey)
138            .token_account_owner(*token_account_owner_pubkey)
139            .mint_config(*mint_config_pubkey)
140            .token_program(*token_program_pubkey)
141            .system_program(solana_system_interface::program::ID)
142            .flag_account(flag_account)
143            .instruction()
144    };
145
146    if mint_config.gating_program != Pubkey::default() {
147        token_acl_interface::offchain::add_extra_account_metas_for_freeze(
148            &mut ix,
149            &mint_config.gating_program,
150            signer_pubkey,
151            token_account_pubkey,
152            mint_pubkey,
153            token_account_owner_pubkey,
154            &flag_account,
155            fetch_account_data_fn,
156        )
157        .await?;
158    }
159
160    Ok(ix)
161}
162
163#[cfg(feature = "fetch")]
164pub async fn create_ata_and_thaw_permissionless(
165    rpc: &nonblocking::rpc_client::RpcClient,
166    payer_pubkey: &Pubkey,
167    mint_pubkey: &Pubkey,
168    token_account_owner_pubkey: &Pubkey,
169    idempotent: bool,
170) -> Result<Vec<Instruction>, AccountFetchError> {
171    let fetch_account_data_fn = |pubkey: Pubkey| async move {
172        rpc.get_account_data(&pubkey)
173            .await
174            .map(|data| Some(data.to_vec()))
175            .map_err(Into::<AccountFetchError>::into)
176    };
177
178    create_ata_and_thaw_permissionless_instructions(
179        payer_pubkey,
180        mint_pubkey,
181        &SPL_TOKEN_2022_ID,
182        token_account_owner_pubkey,
183        idempotent,
184        &fetch_account_data_fn,
185    )
186    .await
187}
188
189#[allow(clippy::too_many_arguments)]
190pub async fn create_ata_and_thaw_permissionless_instructions<F, Fut>(
191    payer_pubkey: &Pubkey,
192    mint_pubkey: &Pubkey,
193    token_program_pubkey: &Pubkey,
194    token_account_owner_pubkey: &Pubkey,
195    idempotent: bool,
196    fetch_account_data_fn: &F,
197) -> Result<Vec<Instruction>, AccountFetchError>
198where
199    F: Fn(Pubkey) -> Fut,
200    Fut: Future<Output = AccountDataResult>,
201{
202    let token_account = get_associated_token_address_with_program_id(
203        &token_account_owner_pubkey,
204        &mint_pubkey,
205        &SPL_TOKEN_2022_ID,
206    );
207
208    let ix = if idempotent {
209        create_associated_token_account_idempotent(
210            &payer_pubkey,
211            &token_account_owner_pubkey,
212            &mint_pubkey,
213            &SPL_TOKEN_2022_ID,
214        )
215    } else {
216        create_associated_token_account(
217            &payer_pubkey,
218            &token_account_owner_pubkey,
219            &mint_pubkey,
220            &SPL_TOKEN_2022_ID,
221        )
222    };
223    let mut instructions = vec![ix];
224
225    // assume account doesn't exist, so we mock it
226    let acc = Account {
227        mint: *mint_pubkey,
228        owner: *token_account_owner_pubkey,
229        amount: 0,
230        delegate: COption::None,
231        state: AccountState::Frozen,
232        is_native: COption::None,
233        delegated_amount: 0,
234        close_authority: COption::None,
235    };
236
237    let mut data = vec![0u8; Account::LEN];
238    Account::pack(acc, &mut data)?;
239
240    let mint_data = fetch_account_data_fn(*mint_pubkey)
241        .await?
242        .ok_or(Into::<ProgramError>::into(TokenAclError::InvalidTokenMint))?;
243
244    let mint_config_pubkey = crate::accounts::MintConfig::find_pda(mint_pubkey).0;
245    let gating_program = get_gating_program_from_mint_data(&mint_data);
246    let flag_account = crate::accounts::FlagAccount::find_pda(&token_account).0;
247
248    if let Ok(gating_program) = gating_program {
249        let mut ix = if idempotent {
250            crate::instructions::ThawPermissionlessIdempotentBuilder::new()
251                .gating_program(gating_program)
252                .authority(*payer_pubkey)
253                .mint(*mint_pubkey)
254                .token_account(token_account)
255                .token_account_owner(*token_account_owner_pubkey)
256                .mint_config(mint_config_pubkey)
257                .token_program(*token_program_pubkey)
258                .flag_account(flag_account)
259                .system_program(solana_system_interface::program::ID)
260                .instruction()
261        } else {
262            crate::instructions::ThawPermissionlessBuilder::new()
263                .gating_program(gating_program)
264                .authority(*payer_pubkey)
265                .mint(*mint_pubkey)
266                .token_account(token_account)
267                .token_account_owner(*token_account_owner_pubkey)
268                .mint_config(mint_config_pubkey)
269                .token_program(*token_program_pubkey)
270                .flag_account(flag_account)
271                .system_program(solana_system_interface::program::ID)
272                .instruction()
273        };
274
275        token_acl_interface::offchain::add_extra_account_metas_for_thaw(
276            &mut ix,
277            &gating_program,
278            payer_pubkey,
279            &token_account,
280            mint_pubkey,
281            token_account_owner_pubkey,
282            &flag_account,
283            |pubkey| {
284                let data = data.clone();
285                async move {
286                    if pubkey == token_account {
287                        return Ok(Some(data));
288                    }
289                    let data = fetch_account_data_fn(pubkey).await.unwrap_or(None);
290                    Ok(data)
291                }
292            },
293        )
294        .await?;
295
296        instructions.push(ix);
297    }
298
299    Ok(instructions)
300}