1use mpl_token_auth_rules::{
2 instruction::{builders::ValidateBuilder, InstructionBuilder, ValidateArgs},
3 payload::PayloadType,
4};
5use mpl_utils::{create_or_allocate_account_raw, token::TokenTransferParams};
6use solana_program::{
7 account_info::AccountInfo, entrypoint::ProgramResult, program::invoke_signed,
8 program_error::ProgramError, program_option::COption, pubkey::Pubkey,
9};
10use spl_token::{
11 instruction::{freeze_account, thaw_account, AuthorityType as SplAuthorityType},
12 state::Account,
13};
14
15use crate::{
16 assertions::{assert_derivation, programmable::assert_valid_authorization},
17 edition_seeds,
18 error::MetadataError,
19 pda::{EDITION, PREFIX},
20 processor::{AuthorizationData, TransferScenario},
21 state::{
22 Operation, PayloadKey, ProgrammableConfig, Resizable, ToAccountMeta, TokenMetadataAccount,
23 TokenRecord, TOKEN_RECORD_SEED,
24 },
25};
26
27pub fn create_token_record_account<'a>(
28 program_id: &Pubkey,
29 token_record_info: &'a AccountInfo<'a>,
30 mint_info: &'a AccountInfo<'a>,
31 token_info: &'a AccountInfo<'a>,
32 payer_info: &'a AccountInfo<'a>,
33 system_program_info: &'a AccountInfo<'a>,
34) -> ProgramResult {
35 if !token_record_info.data_is_empty() {
36 return Err(MetadataError::DelegateAlreadyExists.into());
37 }
38
39 let mut signer_seeds = Vec::from([
40 PREFIX.as_bytes(),
41 crate::ID.as_ref(),
42 mint_info.key.as_ref(),
43 TOKEN_RECORD_SEED.as_bytes(),
44 token_info.key.as_ref(),
45 ]);
46
47 let bump = &[assert_derivation(
48 program_id,
49 token_record_info,
50 &signer_seeds,
51 )?];
52 signer_seeds.push(bump);
53
54 create_or_allocate_account_raw(
57 *program_id,
58 token_record_info,
59 system_program_info,
60 payer_info,
61 TokenRecord::size(),
62 &signer_seeds,
63 )?;
64
65 let token_record = TokenRecord {
66 bump: bump[0],
67 ..Default::default()
68 };
69
70 token_record.save(token_record_info, payer_info, system_program_info)
71}
72
73pub fn freeze<'a>(
74 mint: AccountInfo<'a>,
75 token: AccountInfo<'a>,
76 edition: AccountInfo<'a>,
77 spl_token_program: AccountInfo<'a>,
78) -> ProgramResult {
79 let edition_info_path = Vec::from([
80 PREFIX.as_bytes(),
81 crate::ID.as_ref(),
82 mint.key.as_ref(),
83 EDITION.as_bytes(),
84 ]);
85 let edition_info_path_bump_seed =
86 &[assert_derivation(&crate::ID, &edition, &edition_info_path)?];
87 let mut edition_info_seeds = edition_info_path.clone();
88 edition_info_seeds.push(edition_info_path_bump_seed);
89
90 invoke_signed(
91 &freeze_account(spl_token_program.key, token.key, mint.key, edition.key, &[]).unwrap(),
92 &[token, mint, edition],
93 &[&edition_info_seeds],
94 )?;
95 Ok(())
96}
97
98pub fn thaw<'a>(
99 mint_info: AccountInfo<'a>,
100 token_info: AccountInfo<'a>,
101 edition_info: AccountInfo<'a>,
102 spl_token_program: AccountInfo<'a>,
103) -> ProgramResult {
104 let edition_info_path = Vec::from([
105 PREFIX.as_bytes(),
106 crate::ID.as_ref(),
107 mint_info.key.as_ref(),
108 EDITION.as_bytes(),
109 ]);
110 let edition_info_path_bump_seed = &[assert_derivation(
111 &crate::ID,
112 &edition_info,
113 &edition_info_path,
114 )?];
115 let mut edition_info_seeds = edition_info_path.clone();
116 edition_info_seeds.push(edition_info_path_bump_seed);
117
118 invoke_signed(
119 &thaw_account(
120 spl_token_program.key,
121 token_info.key,
122 mint_info.key,
123 edition_info.key,
124 &[],
125 )
126 .unwrap(),
127 &[token_info, mint_info, edition_info],
128 &[&edition_info_seeds],
129 )?;
130 Ok(())
131}
132
133pub fn validate<'a>(
134 ruleset: &'a AccountInfo<'a>,
135 operation: Operation,
136 mint_info: &'a AccountInfo<'a>,
137 additional_rule_accounts: Vec<&'a AccountInfo<'a>>,
138 auth_data: &AuthorizationData,
139 rule_set_revision: Option<usize>,
140) -> Result<(), ProgramError> {
141 let account_metas = additional_rule_accounts
142 .iter()
143 .map(|account| account.to_account_meta())
144 .collect();
145
146 let validate_ix = ValidateBuilder::new()
147 .rule_set_pda(*ruleset.key)
148 .mint(*mint_info.key)
149 .additional_rule_accounts(account_metas)
150 .build(ValidateArgs::V1 {
151 operation: operation.to_string(),
152 payload: auth_data.payload.clone(),
153 update_rule_state: false,
154 rule_set_revision,
155 })
156 .map_err(|_error| MetadataError::InvalidAuthorizationRules)?
157 .instruction();
158
159 let mut account_infos = vec![ruleset.clone(), mint_info.clone()];
160 account_infos.extend(additional_rule_accounts.into_iter().cloned());
161 invoke_signed(&validate_ix, account_infos.as_slice(), &[])
162}
163
164#[derive(Debug, Clone)]
165pub struct AuthRulesValidateParams<'a> {
166 pub mint_info: &'a AccountInfo<'a>,
167 pub source_info: Option<&'a AccountInfo<'a>>,
168 pub destination_info: Option<&'a AccountInfo<'a>>,
169 pub authority_info: Option<&'a AccountInfo<'a>>,
170 pub owner_info: Option<&'a AccountInfo<'a>>,
171 pub programmable_config: Option<ProgrammableConfig>,
172 pub amount: u64,
173 pub auth_data: Option<AuthorizationData>,
174 pub auth_rules_info: Option<&'a AccountInfo<'a>>,
175 pub operation: Operation,
176 pub is_wallet_to_wallet: bool,
177 pub rule_set_revision: Option<usize>,
178}
179
180pub fn auth_rules_validate(params: AuthRulesValidateParams) -> ProgramResult {
181 let AuthRulesValidateParams {
182 mint_info,
183 owner_info,
184 source_info,
185 destination_info,
186 authority_info,
187 programmable_config,
188 amount,
189 auth_data,
190 auth_rules_info,
191 operation,
192 is_wallet_to_wallet,
193 rule_set_revision,
194 } = params;
195
196 if is_wallet_to_wallet {
197 return Ok(());
198 }
199
200 if let Operation::Transfer { scenario } = &operation {
201 if matches!(scenario, TransferScenario::MigrationDelegate) {
204 return Ok(());
205 }
206 }
207
208 if let Some(ref config) = programmable_config {
209 if let ProgrammableConfig::V1 { rule_set: Some(_) } = config {
210 assert_valid_authorization(auth_rules_info, config)?;
211
212 let auth_pda = auth_rules_info.unwrap();
215
216 let mut auth_data = if let Some(auth_data) = auth_data {
217 auth_data
218 } else {
219 AuthorizationData::new_empty()
220 };
221
222 let mut additional_rule_accounts = vec![];
223 if let Some(source_info) = source_info {
224 additional_rule_accounts.push(source_info);
225 }
226 if let Some(destination_info) = destination_info {
227 additional_rule_accounts.push(destination_info);
228 }
229 if let Some(authority_info) = authority_info {
230 additional_rule_accounts.push(authority_info);
231 }
232 if let Some(owner_info) = owner_info {
233 additional_rule_accounts.push(owner_info);
234 }
235
236 match operation {
238 Operation::Transfer { scenario: _ } => {
239 let authority_info = authority_info.ok_or(MetadataError::InvalidOperation)?;
241 let source_info = source_info.ok_or(MetadataError::InvalidOperation)?;
242 let destination_info =
243 destination_info.ok_or(MetadataError::InvalidOperation)?;
244
245 auth_data
247 .payload
248 .insert(PayloadKey::Amount.to_string(), PayloadType::Number(amount));
249
250 auth_data.payload.insert(
252 PayloadKey::Authority.to_string(),
253 PayloadType::Pubkey(*authority_info.key),
254 );
255
256 auth_data.payload.insert(
258 PayloadKey::Source.to_string(),
259 PayloadType::Pubkey(*source_info.key),
260 );
261
262 auth_data.payload.insert(
264 PayloadKey::Destination.to_string(),
265 PayloadType::Pubkey(*destination_info.key),
266 );
267 }
268 Operation::Delegate { scenario: _ } => {
269 let destination_info =
271 destination_info.ok_or(MetadataError::InvalidOperation)?;
272
273 auth_data
275 .payload
276 .insert(PayloadKey::Amount.to_string(), PayloadType::Number(amount));
277
278 auth_data.payload.insert(
280 PayloadKey::Delegate.to_string(),
281 PayloadType::Pubkey(*destination_info.key),
282 );
283 }
284 _ => {
285 return Err(MetadataError::InvalidOperation.into());
286 }
287 }
288
289 validate(
290 auth_pda,
291 operation,
292 mint_info,
293 additional_rule_accounts,
294 &auth_data,
295 rule_set_revision,
296 )?;
297 }
298 }
299 Ok(())
300}
301
302pub fn frozen_transfer<'a>(
303 params: TokenTransferParams<'a, '_>,
304 edition_opt_info: Option<&'a AccountInfo<'a>>,
305) -> ProgramResult {
306 if edition_opt_info.is_none() {
307 return Err(MetadataError::MissingEditionAccount.into());
308 }
309 let master_edition_info = edition_opt_info.unwrap();
310
311 thaw(
312 params.mint.clone(),
313 params.source.clone(),
314 master_edition_info.clone(),
315 params.token_program.clone(),
316 )?;
317
318 let mint_info = params.mint.clone();
319 let dest_info = params.destination.clone();
320 let token_program_info = params.token_program.clone();
321
322 mpl_utils::token::spl_token_transfer(params).unwrap();
323
324 freeze(
325 mint_info,
326 dest_info.clone(),
327 master_edition_info.clone(),
328 token_program_info.clone(),
329 )?;
330
331 Ok(())
332}
333
334pub(crate) struct ClearCloseAuthorityParams<'a> {
335 pub token: Account,
336 pub mint_info: &'a AccountInfo<'a>,
337 pub token_info: &'a AccountInfo<'a>,
338 pub master_edition_info: &'a AccountInfo<'a>,
339 pub authority_info: &'a AccountInfo<'a>,
340 pub spl_token_program_info: &'a AccountInfo<'a>,
341}
342
343pub(crate) fn clear_close_authority(params: ClearCloseAuthorityParams) -> ProgramResult {
344 let ClearCloseAuthorityParams {
345 token,
346 mint_info,
347 token_info,
348 master_edition_info,
349 authority_info,
350 spl_token_program_info,
351 } = params;
352
353 if let COption::Some(close_authority) = token.close_authority {
356 if &close_authority != master_edition_info.key {
357 return Err(MetadataError::InvalidCloseAuthority.into());
358 }
359 let seeds = edition_seeds!(mint_info.key);
360
361 invoke_signed(
362 &spl_token::instruction::set_authority(
363 spl_token_program_info.key,
364 token_info.key,
365 None,
366 SplAuthorityType::CloseAccount,
367 authority_info.key,
368 &[],
369 )?,
370 &[token_info.clone(), authority_info.clone()],
371 &[seeds.as_slice()],
372 )?;
373 }
374
375 Ok(())
376}