spl_token_wrap/
processor.rs

1//! Program state processor
2
3use {
4    crate::{
5        error::TokenWrapError,
6        get_wrapped_mint_address, get_wrapped_mint_address_with_seed, get_wrapped_mint_authority,
7        get_wrapped_mint_authority_signer_seeds, get_wrapped_mint_authority_with_seed,
8        get_wrapped_mint_backpointer_address_signer_seeds,
9        get_wrapped_mint_backpointer_address_with_seed, get_wrapped_mint_signer_seeds,
10        instruction::TokenWrapInstruction,
11        metadata::extract_token_metadata,
12        metaplex::token_2022_metadata_to_metaplex,
13        mint_customizer::{
14            default_token_2022::DefaultToken2022Customizer, interface::MintCustomizer,
15        },
16        state::Backpointer,
17    },
18    mpl_token_metadata::{
19        accounts::Metadata as MetaplexMetadata,
20        instructions::{
21            CreateMetadataAccountV3, CreateMetadataAccountV3InstructionArgs,
22            UpdateMetadataAccountV2, UpdateMetadataAccountV2InstructionArgs,
23        },
24    },
25    solana_account_info::{next_account_info, AccountInfo},
26    solana_cpi::{invoke, invoke_signed},
27    solana_msg::msg,
28    solana_program_error::{ProgramError, ProgramResult},
29    solana_program_pack::Pack,
30    solana_pubkey::Pubkey,
31    solana_rent::Rent,
32    solana_system_interface::instruction::{allocate, assign},
33    solana_sysvar::{clock::Clock, Sysvar},
34    spl_associated_token_account_client::address::get_associated_token_address_with_program_id,
35    spl_token_2022::{
36        extension::{
37            transfer_fee::TransferFeeConfig, BaseStateWithExtensions, ExtensionType,
38            PodStateWithExtensions,
39        },
40        instruction::initialize_mint2,
41        onchain::{
42            extract_multisig_accounts, invoke_transfer_checked, invoke_transfer_checked_with_fee,
43        },
44        pod::{PodAccount, PodMint},
45        state::AccountState,
46    },
47    spl_token_metadata_interface::{
48        instruction::{initialize as initialize_token_metadata, remove_key, update_field},
49        state::{Field, TokenMetadata},
50    },
51    std::collections::HashMap,
52};
53
54/// Processes [`CreateMint`](enum.TokenWrapInstruction.html) instruction.
55pub fn process_create_mint<M: MintCustomizer>(
56    program_id: &Pubkey,
57    accounts: &[AccountInfo],
58    idempotent: bool,
59) -> ProgramResult {
60    let account_info_iter = &mut accounts.iter();
61
62    let wrapped_mint_account = next_account_info(account_info_iter)?;
63    let wrapped_backpointer_account = next_account_info(account_info_iter)?;
64    let unwrapped_mint_account = next_account_info(account_info_iter)?;
65    let _system_program_account = next_account_info(account_info_iter)?;
66    let wrapped_token_program_account = next_account_info(account_info_iter)?;
67
68    let (wrapped_mint_address, mint_bump) = get_wrapped_mint_address_with_seed(
69        unwrapped_mint_account.key,
70        wrapped_token_program_account.key,
71    );
72
73    let (wrapped_backpointer_address, backpointer_bump) =
74        get_wrapped_mint_backpointer_address_with_seed(wrapped_mint_account.key);
75
76    // PDA derivation validation
77
78    if *wrapped_mint_account.key != wrapped_mint_address {
79        Err(TokenWrapError::WrappedMintMismatch)?
80    }
81
82    if *wrapped_backpointer_account.key != wrapped_backpointer_address {
83        Err(TokenWrapError::BackpointerMismatch)?
84    }
85
86    // The *unwrapped mint* must itself be a real SPL‑Token mint
87    if unwrapped_mint_account.owner != &spl_token::id()
88        && unwrapped_mint_account.owner != &spl_token_2022::id()
89    {
90        Err(ProgramError::InvalidAccountOwner)?
91    }
92
93    // Idempotency checks
94
95    if wrapped_mint_account.data_len() > 0 || wrapped_backpointer_account.data_len() > 0 {
96        msg!("Wrapped mint or backpointer account already initialized");
97        if !idempotent {
98            Err(ProgramError::AccountAlreadyInitialized)?
99        }
100        if wrapped_mint_account.owner != wrapped_token_program_account.key {
101            Err(TokenWrapError::InvalidWrappedMintOwner)?
102        }
103        if wrapped_backpointer_account.owner != program_id {
104            Err(TokenWrapError::InvalidBackpointerOwner)?
105        }
106        return Ok(());
107    }
108
109    // Initialize wrapped mint PDA
110
111    let bump_seed = [mint_bump];
112    let signer_seeds = get_wrapped_mint_signer_seeds(
113        unwrapped_mint_account.key,
114        wrapped_token_program_account.key,
115        &bump_seed,
116    );
117
118    let space = if *wrapped_token_program_account.key == spl_token_2022::id() {
119        M::get_token_2022_mint_space()?
120    } else {
121        spl_token::state::Mint::get_packed_len()
122    };
123
124    let rent = Rent::get()?;
125    let mint_rent_required = rent.minimum_balance(space);
126    if wrapped_mint_account.lamports() < mint_rent_required {
127        msg!(
128            "Error: wrapped_mint_account requires pre-funding of {} lamports",
129            mint_rent_required
130        );
131        Err(ProgramError::AccountNotRentExempt)?
132    }
133
134    // Initialize the wrapped mint
135
136    invoke_signed(
137        &allocate(&wrapped_mint_address, space as u64),
138        &[wrapped_mint_account.clone()],
139        &[&signer_seeds],
140    )?;
141    invoke_signed(
142        &assign(&wrapped_mint_address, wrapped_token_program_account.key),
143        &[wrapped_mint_account.clone()],
144        &[&signer_seeds],
145    )?;
146
147    if *wrapped_token_program_account.key == spl_token_2022::id() {
148        M::initialize_extensions(wrapped_mint_account, wrapped_token_program_account)?;
149    }
150
151    let (freeze_authority, decimals) = M::get_freeze_auth_and_decimals(unwrapped_mint_account)?;
152    let wrapped_mint_authority = get_wrapped_mint_authority(wrapped_mint_account.key);
153
154    invoke(
155        &initialize_mint2(
156            wrapped_token_program_account.key,
157            wrapped_mint_account.key,
158            &wrapped_mint_authority,
159            freeze_authority.as_ref(),
160            decimals,
161        )?,
162        &[wrapped_mint_account.clone()],
163    )?;
164
165    // Initialize backpointer PDA
166
167    let backpointer_space = std::mem::size_of::<Backpointer>();
168    let backpointer_rent_required = rent.minimum_balance(backpointer_space);
169    if wrapped_backpointer_account.lamports() < backpointer_rent_required {
170        msg!(
171            "Error: wrapped_backpointer_account requires pre-funding of {} lamports",
172            backpointer_rent_required
173        );
174        Err(ProgramError::AccountNotRentExempt)?
175    }
176
177    let bump_seed = [backpointer_bump];
178    let backpointer_signer_seeds =
179        get_wrapped_mint_backpointer_address_signer_seeds(wrapped_mint_account.key, &bump_seed);
180    invoke_signed(
181        &allocate(&wrapped_backpointer_address, backpointer_space as u64),
182        &[wrapped_backpointer_account.clone()],
183        &[&backpointer_signer_seeds],
184    )?;
185    invoke_signed(
186        &assign(&wrapped_backpointer_address, program_id),
187        &[wrapped_backpointer_account.clone()],
188        &[&backpointer_signer_seeds],
189    )?;
190
191    // Set data within backpointer PDA
192
193    let mut backpointer_account_data = wrapped_backpointer_account.try_borrow_mut_data()?;
194    let backpointer = bytemuck::from_bytes_mut::<Backpointer>(&mut backpointer_account_data[..]);
195    backpointer.unwrapped_mint = *unwrapped_mint_account.key;
196
197    Ok(())
198}
199
200/// Processes [`Wrap`](enum.TokenWrapInstruction.html) instruction.
201pub fn process_wrap(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
202    if amount == 0 {
203        Err(TokenWrapError::ZeroWrapAmount)?
204    }
205
206    let account_info_iter = &mut accounts.iter();
207
208    let recipient_wrapped_token_account = next_account_info(account_info_iter)?;
209    let wrapped_mint = next_account_info(account_info_iter)?;
210    let wrapped_mint_authority = next_account_info(account_info_iter)?;
211    let unwrapped_token_program = next_account_info(account_info_iter)?;
212    let wrapped_token_program = next_account_info(account_info_iter)?;
213    let unwrapped_token_account = next_account_info(account_info_iter)?;
214    let unwrapped_mint = next_account_info(account_info_iter)?;
215    let unwrapped_escrow = next_account_info(account_info_iter)?;
216    let transfer_authority = next_account_info(account_info_iter)?;
217
218    // Validate accounts
219
220    let expected_wrapped_mint =
221        get_wrapped_mint_address(unwrapped_mint.key, wrapped_token_program.key);
222    if expected_wrapped_mint != *wrapped_mint.key {
223        Err(TokenWrapError::WrappedMintMismatch)?
224    }
225
226    let (expected_authority, bump) = get_wrapped_mint_authority_with_seed(wrapped_mint.key);
227    if *wrapped_mint_authority.key != expected_authority {
228        Err(TokenWrapError::MintAuthorityMismatch)?
229    }
230
231    let expected_escrow = get_associated_token_address_with_program_id(
232        wrapped_mint_authority.key,
233        unwrapped_mint.key,
234        unwrapped_token_program.key,
235    );
236
237    if *unwrapped_escrow.key != expected_escrow {
238        Err(TokenWrapError::EscrowMismatch)?
239    }
240
241    {
242        let escrow_data = unwrapped_escrow.try_borrow_data()?;
243        let escrow_account = PodStateWithExtensions::<PodAccount>::unpack(&escrow_data)?;
244        if escrow_account.base.owner != expected_authority {
245            Err(TokenWrapError::EscrowOwnerMismatch)?
246        }
247    }
248
249    // Transfer unwrapped tokens from user to escrow
250
251    let unwrapped_mint_data = unwrapped_mint.try_borrow_data()?;
252    let unwrapped_mint_state = PodStateWithExtensions::<PodMint>::unpack(&unwrapped_mint_data)?;
253
254    // Calculate amount to mint (subtracting for possible transfer fee)
255    let clock = Clock::get()?.epoch;
256    let fee = unwrapped_mint_state
257        .get_extension::<TransferFeeConfig>()
258        .ok()
259        .and_then(|cfg| cfg.calculate_epoch_fee(clock, amount))
260        .unwrap_or(0);
261    let net_amount = amount
262        .checked_sub(fee)
263        .ok_or(ProgramError::ArithmeticOverflow)?;
264
265    if unwrapped_token_program.key == &spl_token_2022::id() {
266        // This invoke fn does extra validation on calculated fee
267        invoke_transfer_checked_with_fee(
268            unwrapped_token_program.key,
269            unwrapped_token_account.clone(),
270            unwrapped_mint.clone(),
271            unwrapped_escrow.clone(),
272            transfer_authority.clone(),
273            &accounts[9..],
274            amount,
275            unwrapped_mint_state.base.decimals,
276            fee,
277            &[],
278        )?;
279    } else {
280        invoke_transfer_checked(
281            unwrapped_token_program.key,
282            unwrapped_token_account.clone(),
283            unwrapped_mint.clone(),
284            unwrapped_escrow.clone(),
285            transfer_authority.clone(),
286            &accounts[9..],
287            amount,
288            unwrapped_mint_state.base.decimals,
289            &[],
290        )?;
291    }
292
293    // Mint wrapped tokens to recipient
294    let bump_seed = [bump];
295    let signer_seeds = get_wrapped_mint_authority_signer_seeds(wrapped_mint.key, &bump_seed);
296
297    invoke_signed(
298        &spl_token_2022::instruction::mint_to(
299            wrapped_token_program.key,
300            wrapped_mint.key,
301            recipient_wrapped_token_account.key,
302            wrapped_mint_authority.key,
303            &[],
304            net_amount,
305        )?,
306        &[
307            wrapped_mint.clone(),
308            recipient_wrapped_token_account.clone(),
309            wrapped_mint_authority.clone(),
310        ],
311        &[&signer_seeds],
312    )?;
313
314    Ok(())
315}
316
317/// Processes [`Unwrap`](enum.TokenWrapInstruction.html) instruction.
318pub fn process_unwrap(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
319    if amount == 0 {
320        Err(TokenWrapError::ZeroWrapAmount)?
321    }
322
323    let account_info_iter = &mut accounts.iter();
324
325    let unwrapped_escrow = next_account_info(account_info_iter)?;
326    let recipient_unwrapped_token = next_account_info(account_info_iter)?;
327    let wrapped_mint_authority = next_account_info(account_info_iter)?;
328    let unwrapped_mint = next_account_info(account_info_iter)?;
329    let wrapped_token_program = next_account_info(account_info_iter)?;
330    let unwrapped_token_program = next_account_info(account_info_iter)?;
331    let wrapped_token_account = next_account_info(account_info_iter)?;
332    let wrapped_mint = next_account_info(account_info_iter)?;
333    let transfer_authority = next_account_info(account_info_iter)?;
334    let additional_accounts = account_info_iter.as_slice();
335
336    // Validate accounts
337
338    let expected_wrapped_mint =
339        get_wrapped_mint_address(unwrapped_mint.key, wrapped_token_program.key);
340    if expected_wrapped_mint != *wrapped_mint.key {
341        Err(TokenWrapError::WrappedMintMismatch)?
342    }
343
344    let (expected_authority, bump) = get_wrapped_mint_authority_with_seed(wrapped_mint.key);
345    if *wrapped_mint_authority.key != expected_authority {
346        Err(TokenWrapError::MintAuthorityMismatch)?
347    }
348
349    let expected_escrow = get_associated_token_address_with_program_id(
350        wrapped_mint_authority.key,
351        unwrapped_mint.key,
352        unwrapped_token_program.key,
353    );
354    if *unwrapped_escrow.key != expected_escrow {
355        Err(TokenWrapError::EscrowMismatch)?
356    }
357
358    // Burn wrapped tokens
359
360    let multisig_signer_keys = extract_multisig_accounts(transfer_authority, additional_accounts)?
361        .iter()
362        .map(|a| a.key)
363        .collect::<Vec<_>>();
364
365    invoke(
366        &spl_token_2022::instruction::burn(
367            wrapped_token_program.key,
368            wrapped_token_account.key,
369            wrapped_mint.key,
370            transfer_authority.key,
371            &multisig_signer_keys,
372            amount,
373        )?,
374        &accounts[6..],
375    )?;
376
377    // Transfer unwrapped tokens from escrow to recipient
378
379    let unwrapped_mint_data = unwrapped_mint.try_borrow_data()?;
380    let unwrapped_mint_state = PodStateWithExtensions::<PodMint>::unpack(&unwrapped_mint_data)?;
381    let bump_seed = [bump];
382    let signer_seeds = get_wrapped_mint_authority_signer_seeds(wrapped_mint.key, &bump_seed);
383
384    invoke_transfer_checked(
385        unwrapped_token_program.key,
386        unwrapped_escrow.clone(),
387        unwrapped_mint.clone(),
388        recipient_unwrapped_token.clone(),
389        wrapped_mint_authority.clone(),
390        additional_accounts,
391        amount,
392        unwrapped_mint_state.base.decimals,
393        &[&signer_seeds],
394    )?;
395
396    Ok(())
397}
398
399/// Processes [`CloseStuckEscrow`](enum.TokenWrapInstruction.html) instruction.
400pub fn process_close_stuck_escrow(accounts: &[AccountInfo]) -> ProgramResult {
401    let account_info_iter = &mut accounts.iter();
402
403    let escrow_account = next_account_info(account_info_iter)?;
404    let destination_account = next_account_info(account_info_iter)?;
405    let unwrapped_mint = next_account_info(account_info_iter)?;
406    let wrapped_mint = next_account_info(account_info_iter)?;
407    let wrapped_mint_authority = next_account_info(account_info_iter)?;
408    let _token_2022_program = next_account_info(account_info_iter)?;
409
410    // This instruction is only for spl-token-2022 accounts because only they
411    // can have extensions that lead to size changes.
412    if *escrow_account.owner != spl_token_2022::id()
413        || unwrapped_mint.owner != &spl_token_2022::id()
414    {
415        return Err(ProgramError::IncorrectProgramId);
416    }
417
418    let expected_wrapped_mint_pubkey =
419        get_wrapped_mint_address(unwrapped_mint.key, wrapped_mint.owner);
420    if *wrapped_mint.key != expected_wrapped_mint_pubkey {
421        Err(TokenWrapError::WrappedMintMismatch)?
422    }
423
424    let (expected_authority, bump) = get_wrapped_mint_authority_with_seed(wrapped_mint.key);
425    if *wrapped_mint_authority.key != expected_authority {
426        Err(TokenWrapError::MintAuthorityMismatch)?
427    }
428
429    let expected_escrow_address = get_associated_token_address_with_program_id(
430        wrapped_mint_authority.key,
431        unwrapped_mint.key,
432        unwrapped_mint.owner,
433    );
434
435    if *escrow_account.key != expected_escrow_address {
436        return Err(TokenWrapError::EscrowMismatch.into());
437    }
438
439    let escrow_data = escrow_account.try_borrow_data()?;
440    let escrow_state = PodStateWithExtensions::<PodAccount>::unpack(&escrow_data)?;
441
442    if escrow_state.base.owner != *wrapped_mint_authority.key {
443        return Err(TokenWrapError::EscrowOwnerMismatch.into());
444    }
445
446    // Closing only works when the token balance is zero
447    if u64::from(escrow_state.base.amount) != 0 {
448        return Err(ProgramError::InvalidAccountData);
449    }
450
451    // Ensure the account is in the initialized state
452    if escrow_state.base.state != (AccountState::Initialized as u8) {
453        return Err(ProgramError::InvalidAccountData);
454    }
455
456    let current_account_extensions = escrow_state.get_extension_types()?;
457    drop(escrow_data);
458
459    let mint_data = unwrapped_mint.try_borrow_data()?;
460    let mint_state = PodStateWithExtensions::<PodMint>::unpack(&mint_data)?;
461    let mint_extensions = mint_state.get_extension_types()?;
462    let mut required_account_extensions =
463        ExtensionType::get_required_init_account_extensions(&mint_extensions);
464
465    // ATAs always have the ImmutableOwner extension
466    if !required_account_extensions.contains(&ExtensionType::ImmutableOwner) {
467        required_account_extensions.push(ExtensionType::ImmutableOwner);
468    }
469
470    // If the token account already shares the same extensions as the mint,
471    // it does not need to be re-created
472    let in_good_state = current_account_extensions.len() == required_account_extensions.len()
473        && required_account_extensions
474            .iter()
475            .all(|item| current_account_extensions.contains(item));
476
477    if in_good_state {
478        return Err(TokenWrapError::EscrowInGoodState.into());
479    }
480
481    // Close old escrow account
482    let bump_seed = [bump];
483    let signer_seeds = get_wrapped_mint_authority_signer_seeds(wrapped_mint.key, &bump_seed);
484
485    invoke_signed(
486        &spl_token_2022::instruction::close_account(
487            escrow_account.owner,
488            escrow_account.key,
489            destination_account.key,
490            wrapped_mint_authority.key,
491            &[],
492        )?,
493        &[
494            escrow_account.clone(),
495            destination_account.clone(),
496            wrapped_mint_authority.clone(),
497        ],
498        &[&signer_seeds],
499    )?;
500
501    Ok(())
502}
503
504type FieldExtractor = Vec<(Field, fn(&TokenMetadata) -> &str)>;
505
506fn update_fields_if_changed<'a>(
507    token_program: &Pubkey,
508    mint: &AccountInfo<'a>,
509    authority: &AccountInfo<'a>,
510    fields: FieldExtractor,
511    wrapped_metadata: &TokenMetadata,
512    unwrapped_metadata: &TokenMetadata,
513    signer_seeds: &[&[&[u8]]],
514) -> ProgramResult {
515    for (field, extractor) in fields {
516        let current_value = extractor(wrapped_metadata);
517        let new_value = extractor(unwrapped_metadata);
518        if current_value != new_value {
519            invoke_signed(
520                &update_field(
521                    token_program,
522                    mint.key,
523                    authority.key,
524                    field.clone(),
525                    new_value.to_string(),
526                ),
527                &[mint.clone(), authority.clone()],
528                signer_seeds,
529            )?;
530        }
531    }
532    Ok(())
533}
534
535/// Processes [`SyncMetadataToToken2022`](enum.TokenWrapInstruction.html)
536/// instruction.
537pub fn process_sync_metadata_to_token_2022(accounts: &[AccountInfo]) -> ProgramResult {
538    let account_info_iter = &mut accounts.iter();
539    let wrapped_mint_info = next_account_info(account_info_iter)?;
540    let wrapped_mint_authority_info = next_account_info(account_info_iter)?;
541    let unwrapped_mint_info = next_account_info(account_info_iter)?;
542    let token_program_info = next_account_info(account_info_iter)?;
543    let source_metadata_info = account_info_iter.next();
544    let owner_program_info = account_info_iter.next();
545
546    if *token_program_info.key != spl_token_2022::id() {
547        return Err(ProgramError::IncorrectProgramId);
548    }
549
550    if *wrapped_mint_info.owner != spl_token_2022::id() {
551        return Err(ProgramError::IncorrectProgramId);
552    }
553
554    let (expected_wrapped_mint, _) =
555        get_wrapped_mint_address_with_seed(unwrapped_mint_info.key, &spl_token_2022::id());
556    if *wrapped_mint_info.key != expected_wrapped_mint {
557        return Err(TokenWrapError::WrappedMintMismatch.into());
558    }
559    let (expected_authority, authority_bump) =
560        get_wrapped_mint_authority_with_seed(wrapped_mint_info.key);
561    if *wrapped_mint_authority_info.key != expected_authority {
562        return Err(TokenWrapError::MintAuthorityMismatch.into());
563    }
564
565    let unwrapped_metadata = extract_token_metadata(
566        unwrapped_mint_info,
567        source_metadata_info,
568        owner_program_info,
569    )?;
570
571    let authority_bump_seed = [authority_bump];
572    let authority_signer_seeds =
573        get_wrapped_mint_authority_signer_seeds(wrapped_mint_info.key, &authority_bump_seed);
574
575    let wrapped_mint_data = wrapped_mint_info.try_borrow_data()?;
576    let wrapped_mint_metadata = PodStateWithExtensions::<PodMint>::unpack(&wrapped_mint_data)?
577        .get_variable_len_extension::<TokenMetadata>()
578        .ok();
579    drop(wrapped_mint_data);
580
581    let cpi_accounts = [
582        wrapped_mint_info.clone(),
583        wrapped_mint_authority_info.clone(),
584    ];
585
586    if let Some(wrapped_metadata) = wrapped_mint_metadata {
587        // The metadata extension is initialized. Update the fields that have changed.
588        // Note that mint and update authority cannot change as that is governed by
589        // token-wrap.
590
591        // --- Sync base fields ---
592        let base_fields: FieldExtractor = vec![
593            (Field::Name, |m| &m.name),
594            (Field::Symbol, |m| &m.symbol),
595            (Field::Uri, |m| &m.uri),
596        ];
597
598        update_fields_if_changed(
599            token_program_info.key,
600            wrapped_mint_info,
601            wrapped_mint_authority_info,
602            base_fields,
603            &wrapped_metadata,
604            &unwrapped_metadata,
605            &[&authority_signer_seeds],
606        )?;
607
608        // --- Sync additional metadata fields ---
609        let mut wrapped_meta_map: HashMap<String, String> =
610            wrapped_metadata.additional_metadata.into_iter().collect();
611
612        for (key, value) in &unwrapped_metadata.additional_metadata {
613            // Update if the key is not present or if the value is different
614            if wrapped_meta_map.get(key) != Some(value) {
615                invoke_signed(
616                    &update_field(
617                        token_program_info.key,
618                        wrapped_mint_info.key,
619                        wrapped_mint_authority_info.key,
620                        Field::Key(key.clone()),
621                        value.clone(),
622                    ),
623                    &cpi_accounts,
624                    &[&authority_signer_seeds],
625                )?;
626            }
627            // Remove the key from the map so we can track deletions
628            wrapped_meta_map.remove(key);
629        }
630
631        // Any keys remaining in the map no longer exist in the source, so remove them
632        for key in wrapped_meta_map.keys() {
633            invoke_signed(
634                &remove_key(
635                    token_program_info.key,
636                    wrapped_mint_info.key,
637                    wrapped_mint_authority_info.key,
638                    key.clone(),
639                    false,
640                ),
641                &cpi_accounts,
642                &[&authority_signer_seeds],
643            )?;
644        }
645    } else {
646        // The wrapped mint does not have the metadata extension. Initialize it with the
647        // fields from the unwrapped mint.
648        invoke_signed(
649            &initialize_token_metadata(
650                token_program_info.key,
651                wrapped_mint_info.key,
652                wrapped_mint_authority_info.key,
653                wrapped_mint_info.key,
654                wrapped_mint_authority_info.key,
655                unwrapped_metadata.name.clone(),
656                unwrapped_metadata.symbol.clone(),
657                unwrapped_metadata.uri.clone(),
658            ),
659            &cpi_accounts,
660            &[&authority_signer_seeds],
661        )?;
662
663        // After initializing, add the additional metadata fields.
664        for (key, value) in &unwrapped_metadata.additional_metadata {
665            invoke_signed(
666                &update_field(
667                    token_program_info.key,
668                    wrapped_mint_info.key,
669                    wrapped_mint_authority_info.key,
670                    Field::Key(key.clone()),
671                    value.clone(),
672                ),
673                &cpi_accounts,
674                &[&authority_signer_seeds],
675            )?;
676        }
677    }
678
679    Ok(())
680}
681
682/// Processes [`SyncMetadataToSplToken`](enum.TokenWrapInstruction.html)
683/// instruction.
684pub fn process_sync_metadata_to_spl_token(accounts: &[AccountInfo]) -> ProgramResult {
685    let account_info_iter = &mut accounts.iter();
686    let metaplex_metadata_info = next_account_info(account_info_iter)?;
687    let wrapped_mint_authority_info = next_account_info(account_info_iter)?;
688    let wrapped_mint_info = next_account_info(account_info_iter)?;
689    let unwrapped_mint_info = next_account_info(account_info_iter)?;
690    let metaplex_program_info = next_account_info(account_info_iter)?;
691    let system_program_info = next_account_info(account_info_iter)?;
692    let rent_sysvar_info = next_account_info(account_info_iter)?;
693    let source_metadata_info = account_info_iter.next();
694    let owner_program_info = account_info_iter.next();
695
696    // ====== Validations ======
697
698    if *wrapped_mint_info.owner != spl_token::id() {
699        return Err(TokenWrapError::NoSyncingToToken2022.into());
700    }
701
702    let (expected_wrapped_mint, _) =
703        get_wrapped_mint_address_with_seed(unwrapped_mint_info.key, &spl_token::id());
704    if *wrapped_mint_info.key != expected_wrapped_mint {
705        return Err(TokenWrapError::WrappedMintMismatch.into());
706    }
707
708    let (expected_authority, authority_bump) =
709        get_wrapped_mint_authority_with_seed(wrapped_mint_info.key);
710    if *wrapped_mint_authority_info.key != expected_authority {
711        return Err(TokenWrapError::MintAuthorityMismatch.into());
712    }
713
714    let (expected_metaplex_metadata, _) = MetaplexMetadata::find_pda(wrapped_mint_info.key);
715    if *metaplex_metadata_info.key != expected_metaplex_metadata {
716        return Err(TokenWrapError::MetaplexMetadataMismatch.into());
717    }
718
719    if metaplex_program_info.key != &mpl_token_metadata::ID {
720        return Err(ProgramError::IncorrectProgramId);
721    }
722
723    // ====== Extract metadata from source ======
724
725    let unwrapped_metadata = extract_token_metadata(
726        unwrapped_mint_info,
727        source_metadata_info,
728        owner_program_info,
729    )?;
730
731    // ====== Upsert logic ======
732
733    let authority_bump_seed = [authority_bump];
734    let authority_signer_seeds =
735        get_wrapped_mint_authority_signer_seeds(wrapped_mint_info.key, &authority_bump_seed);
736
737    let new_metadata = token_2022_metadata_to_metaplex(&unwrapped_metadata)?;
738
739    // If the Metaplex metadata account is uninitialized, create a new one. The user
740    // is expected to have pre-funded the wrapped_mint_authority PDA. This program
741    // will use that PDA as the payer for the CPI.
742    if metaplex_metadata_info.data_is_empty() {
743        let create_ix = CreateMetadataAccountV3 {
744            metadata: *metaplex_metadata_info.key,
745            mint: *wrapped_mint_info.key,
746            mint_authority: *wrapped_mint_authority_info.key,
747            payer: *wrapped_mint_authority_info.key,
748            update_authority: (*wrapped_mint_authority_info.key, true),
749            system_program: *system_program_info.key,
750            rent: Some(*rent_sysvar_info.key),
751        }
752        .instruction(CreateMetadataAccountV3InstructionArgs {
753            data: new_metadata,
754            is_mutable: true,
755            collection_details: None,
756        });
757
758        invoke_signed(
759            &create_ix,
760            &[
761                metaplex_metadata_info.clone(),
762                wrapped_mint_info.clone(),
763                wrapped_mint_authority_info.clone(),
764                system_program_info.clone(),
765                rent_sysvar_info.clone(),
766            ],
767            &[&authority_signer_seeds],
768        )?;
769    } else {
770        // The Metaplex metadata account is initialized, so update the fields
771        let update_ix = UpdateMetadataAccountV2 {
772            metadata: *metaplex_metadata_info.key,
773            update_authority: *wrapped_mint_authority_info.key,
774        }
775        .instruction(UpdateMetadataAccountV2InstructionArgs {
776            data: Some(new_metadata),
777            // Cannot trust the additional metadata fields on token-2022 & external program case,
778            // so simplifying this and not updating it in any case
779            primary_sale_happened: None,
780            new_update_authority: None,
781            is_mutable: None,
782        });
783        invoke_signed(
784            &update_ix,
785            &[
786                metaplex_metadata_info.clone(),
787                wrapped_mint_authority_info.clone(),
788            ],
789            &[&authority_signer_seeds],
790        )?;
791    }
792
793    Ok(())
794}
795
796/// Instruction processor
797pub fn process_instruction(
798    program_id: &Pubkey,
799    accounts: &[AccountInfo],
800    input: &[u8],
801) -> ProgramResult {
802    match TokenWrapInstruction::unpack(input)? {
803        TokenWrapInstruction::CreateMint { idempotent } => {
804            // === DEVELOPER CUSTOMIZATION POINT ===
805            // To use custom mint creation logic, update the mint customizer argument
806            msg!("Instruction: CreateMint");
807            process_create_mint::<DefaultToken2022Customizer>(program_id, accounts, idempotent)
808        }
809        TokenWrapInstruction::Wrap { amount } => {
810            msg!("Instruction: Wrap");
811            process_wrap(accounts, amount)
812        }
813        TokenWrapInstruction::Unwrap { amount } => {
814            msg!("Instruction: Unwrap");
815            process_unwrap(accounts, amount)
816        }
817        TokenWrapInstruction::CloseStuckEscrow => {
818            msg!("Instruction: CloseStuckEscrow");
819            process_close_stuck_escrow(accounts)
820        }
821        TokenWrapInstruction::SyncMetadataToToken2022 => {
822            msg!("Instruction: SyncMetadataToToken2022");
823            process_sync_metadata_to_token_2022(accounts)
824        }
825        TokenWrapInstruction::SyncMetadataToSplToken => {
826            msg!("Instruction: SyncMetadataToSplToken");
827            process_sync_metadata_to_spl_token(accounts)
828        }
829    }
830}