Ver Fonte

Add support for token extensions (#2789)

Bhargava Sai Macha há 1 ano atrás
pai
commit
e3ced784ad
42 ficheiros alterados com 2463 adições e 23 exclusões
  1. 2 0
      .github/workflows/reusable-tests.yaml
  2. 1 0
      CHANGELOG.md
  3. 11 8
      Cargo.lock
  4. 1 2
      lang/Cargo.toml
  5. 50 0
      lang/src/error.rs
  6. 436 6
      lang/syn/src/codegen/accounts/constraints.rs
  7. 107 0
      lang/syn/src/lib.rs
  8. 441 1
      lang/syn/src/parser/accounts/constraints.rs
  9. 5 1
      spl/Cargo.toml
  10. 3 0
      spl/src/lib.rs
  11. 0 4
      spl/src/token_2022.rs
  12. 1 0
      spl/src/token_2022_extensions/confidential_transfer.rs
  13. 1 0
      spl/src/token_2022_extensions/confidential_transfer_fee.rs
  14. 50 0
      spl/src/token_2022_extensions/cpi_guard.rs
  15. 59 0
      spl/src/token_2022_extensions/default_account_state.rs
  16. 59 0
      spl/src/token_2022_extensions/group_member_pointer.rs
  17. 55 0
      spl/src/token_2022_extensions/group_pointer.rs
  18. 25 0
      spl/src/token_2022_extensions/immutable_owner.rs
  19. 59 0
      spl/src/token_2022_extensions/interest_bearing_mint.rs
  20. 54 0
      spl/src/token_2022_extensions/memo_transfer.rs
  21. 29 0
      spl/src/token_2022_extensions/metadata_pointer.rs
  22. 27 0
      spl/src/token_2022_extensions/mint_close_authority.rs
  23. 36 0
      spl/src/token_2022_extensions/mod.rs
  24. 25 0
      spl/src/token_2022_extensions/non_transferable.rs
  25. 27 0
      spl/src/token_2022_extensions/permanent_delegate.rs
  26. 74 0
      spl/src/token_2022_extensions/token_group.rs
  27. 107 0
      spl/src/token_2022_extensions/token_metadata.rs
  28. 160 0
      spl/src/token_2022_extensions/transfer_fee.rs
  29. 59 0
      spl/src/token_2022_extensions/transfer_hook.rs
  30. 30 0
      spl/src/token_interface.rs
  31. 1 0
      tests/package.json
  32. 14 0
      tests/spl/token-extensions/Anchor.toml
  33. 8 0
      tests/spl/token-extensions/Cargo.toml
  34. 22 0
      tests/spl/token-extensions/package.json
  35. 25 0
      tests/spl/token-extensions/programs/token-extensions/Cargo.toml
  36. 2 0
      tests/spl/token-extensions/programs/token-extensions/Xargo.toml
  37. 180 0
      tests/spl/token-extensions/programs/token-extensions/src/instructions.rs
  38. 32 0
      tests/spl/token-extensions/programs/token-extensions/src/lib.rs
  39. 88 0
      tests/spl/token-extensions/programs/token-extensions/src/utils.rs
  40. 84 0
      tests/spl/token-extensions/tests/token-extensions.ts
  41. 11 0
      tests/spl/token-extensions/tsconfig.json
  42. 2 1
      tests/spl/token-proxy/programs/token-proxy/src/lib.rs

+ 2 - 0
.github/workflows/reusable-tests.yaml

@@ -388,6 +388,8 @@ jobs:
             path: spl/token-wrapper
           - cmd: cd tests/spl/transfer-hook && anchor test --skip-lint
             path: spl/transfer-hook
+          - cmd: cd tests/spl/token-extensions && anchor test --skip-lint
+            path: spl/token-extensions
           - cmd: cd tests/multisig && anchor test --skip-lint
             path: tests/multisig
           # - cmd: cd tests/lockup && anchor test --skip-lint

+ 1 - 0
CHANGELOG.md

@@ -43,6 +43,7 @@ The minor version will be incremented upon a breaking change and the patch versi
 - idl: Add `docs` field for constants ([#2887](https://github.com/coral-xyz/anchor/pull/2887)).
 - idl: Store deployment addresses for other clusters ([#2892](https://github.com/coral-xyz/anchor/pull/2892)).
 - lang: Add `Event` utility type to get events from bytes ([#2897](https://github.com/coral-xyz/anchor/pull/2897)).
+- spl: Add support for [token extensions](https://solana.com/solutions/token-extensions) ([#2789](https://github.com/coral-xyz/anchor/pull/2789)).
 
 ### Fixes
 

+ 11 - 8
Cargo.lock

@@ -309,8 +309,11 @@ dependencies = [
  "solana-program",
  "spl-associated-token-account 3.0.2",
  "spl-memo",
+ "spl-pod 0.2.2",
  "spl-token 4.0.0",
  "spl-token-2022 3.0.2",
+ "spl-token-group-interface 0.2.3",
+ "spl-token-metadata-interface 0.3.3",
 ]
 
 [[package]]
@@ -4779,9 +4782,9 @@ dependencies = [
 
 [[package]]
 name = "spl-pod"
-version = "0.1.0"
+version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2881dddfca792737c0706fa0175345ab282b1b0879c7d877bad129645737c079"
+checksum = "85a5db7e4efb1107b0b8e52a13f035437cdcb36ef99c58f6d467f089d9b2915a"
 dependencies = [
  "borsh 0.10.3",
  "bytemuck",
@@ -4862,7 +4865,7 @@ dependencies = [
  "bytemuck",
  "solana-program",
  "spl-discriminator 0.1.0",
- "spl-pod 0.1.0",
+ "spl-pod 0.1.1",
  "spl-program-error 0.3.0",
  "spl-type-length-value 0.3.0",
 ]
@@ -4926,7 +4929,7 @@ dependencies = [
  "solana-security-txt",
  "solana-zk-token-sdk",
  "spl-memo",
- "spl-pod 0.1.0",
+ "spl-pod 0.1.1",
  "spl-token 4.0.0",
  "spl-token-group-interface 0.1.0",
  "spl-token-metadata-interface 0.2.0",
@@ -4968,7 +4971,7 @@ dependencies = [
  "bytemuck",
  "solana-program",
  "spl-discriminator 0.1.0",
- "spl-pod 0.1.0",
+ "spl-pod 0.1.1",
  "spl-program-error 0.3.0",
 ]
 
@@ -4994,7 +4997,7 @@ dependencies = [
  "borsh 0.10.3",
  "solana-program",
  "spl-discriminator 0.1.0",
- "spl-pod 0.1.0",
+ "spl-pod 0.1.1",
  "spl-program-error 0.3.0",
  "spl-type-length-value 0.3.0",
 ]
@@ -5023,7 +5026,7 @@ dependencies = [
  "bytemuck",
  "solana-program",
  "spl-discriminator 0.1.0",
- "spl-pod 0.1.0",
+ "spl-pod 0.1.1",
  "spl-program-error 0.3.0",
  "spl-tlv-account-resolution 0.5.1",
  "spl-type-length-value 0.3.0",
@@ -5054,7 +5057,7 @@ dependencies = [
  "bytemuck",
  "solana-program",
  "spl-discriminator 0.1.0",
- "spl-pod 0.1.0",
+ "spl-pod 0.1.1",
  "spl-program-error 0.3.0",
 ]
 

+ 1 - 2
lang/Cargo.toml

@@ -59,6 +59,5 @@ borsh = ">=0.9, <0.11"
 bytemuck = "1"
 solana-program = "1.16"
 thiserror = "1"
-
 # TODO: Remove. This crate has been added to fix a build error with the 1.16.0 release.
-getrandom = { version = "0.2", features = ["custom"] }
+getrandom = { version = "0.2", features = ["custom"] }

+ 50 - 0
lang/src/error.rs

@@ -126,6 +126,56 @@ pub enum ErrorCode {
     /// 2023 - A mint token program constraint was violated
     #[msg("An associated token account token program constraint was violated")]
     ConstraintAssociatedTokenTokenProgram,
+    /// Extension constraints
+    ///
+    /// 2024 - A group pointer extension constraint was violated
+    #[msg("A group pointer extension constraint was violated")]
+    ConstraintMintGroupPointerExtension,
+    /// 2025 - A group pointer extension authority constraint was violated
+    #[msg("A group pointer extension authority constraint was violated")]
+    ConstraintMintGroupPointerExtensionAuthority,
+    /// 2026 - A group pointer extension group address constraint was violated
+    #[msg("A group pointer extension group address constraint was violated")]
+    ConstraintMintGroupPointerExtensionGroupAddress,
+    /// 2027 - A group member pointer extension constraint was violated
+    #[msg("A group member pointer extension constraint was violated")]
+    ConstraintMintGroupMemberPointerExtension,
+    /// 2028 - A group member pointer extension authority constraint was violated
+    #[msg("A group member pointer extension authority constraint was violated")]
+    ConstraintMintGroupMemberPointerExtensionAuthority,
+    /// 2029 - A group member pointer extension member address constraint was violated
+    #[msg("A group member pointer extension group address constraint was violated")]
+    ConstraintMintGroupMemberPointerExtensionMemberAddress,
+    /// 2030 - A metadata pointer extension constraint was violated
+    #[msg("A metadata pointer extension constraint was violated")]
+    ConstraintMintMetadataPointerExtension,
+    /// 2031 - A metadata pointer extension authority constraint was violated
+    #[msg("A metadata pointer extension authority constraint was violated")]
+    ConstraintMintMetadataPointerExtensionAuthority,
+    /// 2032 - A metadata pointer extension metadata address constraint was violated
+    #[msg("A metadata pointer extension metadata address constraint was violated")]
+    ConstraintMintMetadataPointerExtensionMetadataAddress,
+    /// 2033 - A close authority extension constraint was violated
+    #[msg("A close authority constraint was violated")]
+    ConstraintMintCloseAuthorityExtension,
+    /// 2034 - A close authority extension authority constraint was violated
+    #[msg("A close authority extension authority constraint was violated")]
+    ConstraintMintCloseAuthorityExtensionAuthority,
+    /// 2035 - A permanent delegate extension constraint was violated
+    #[msg("A permanent delegate extension constraint was violated")]
+    ConstraintMintPermanentDelegateExtension,
+    /// 2036 - A permanent delegate extension authority constraint was violated
+    #[msg("A permanent delegate extension delegate constraint was violated")]
+    ConstraintMintPermanentDelegateExtensionDelegate,
+    /// 2037 - A transfer hook extension constraint was violated
+    #[msg("A transfer hook extension constraint was violated")]
+    ConstraintMintTransferHookExtension,
+    /// 2038 - A transfer hook extension authority constraint was violated
+    #[msg("A transfer hook extension authority constraint was violated")]
+    ConstraintMintTransferHookExtensionAuthority,
+    /// 2039 - A transfer hook extension transfer hook program id constraint was violated
+    #[msg("A transfer hook extension transfer hook program id constraint was violated")]
+    ConstraintMintTransferHookExtensionProgramId,
 
     // Require
     /// 2500 - A require expression was violated

+ 436 - 6
lang/syn/src/codegen/accounts/constraints.rs

@@ -661,6 +661,16 @@ fn generate_constraint_init_group(
             decimals,
             freeze_authority,
             token_program,
+            group_pointer_authority,
+            group_pointer_group_address,
+            group_member_pointer_authority,
+            group_member_pointer_member_address,
+            metadata_pointer_authority,
+            metadata_pointer_metadata_address,
+            close_authority,
+            permanent_delegate,
+            transfer_hook_authority,
+            transfer_hook_program_id,
         } => {
             let token_program = match token_program {
                 Some(t) => t.to_token_stream(),
@@ -672,6 +682,59 @@ fn generate_constraint_init_group(
                 None => quote! {},
             };
 
+            // extension checks
+
+            let group_pointer_authority_check = match group_pointer_authority {
+                Some(gpa) => check_scope.generate_check(gpa),
+                None => quote! {},
+            };
+
+            let group_pointer_group_address_check = match group_pointer_group_address {
+                Some(gpga) => check_scope.generate_check(gpga),
+                None => quote! {},
+            };
+
+            let group_member_pointer_authority_check = match group_member_pointer_authority {
+                Some(gmpa) => check_scope.generate_check(gmpa),
+                None => quote! {},
+            };
+
+            let group_member_pointer_member_address_check =
+                match group_member_pointer_member_address {
+                    Some(gmpm) => check_scope.generate_check(gmpm),
+                    None => quote! {},
+                };
+
+            let metadata_pointer_authority_check = match metadata_pointer_authority {
+                Some(mpa) => check_scope.generate_check(mpa),
+                None => quote! {},
+            };
+
+            let metadata_pointer_metadata_address_check = match metadata_pointer_metadata_address {
+                Some(mpma) => check_scope.generate_check(mpma),
+                None => quote! {},
+            };
+
+            let close_authority_check = match close_authority {
+                Some(ca) => check_scope.generate_check(ca),
+                None => quote! {},
+            };
+
+            let transfer_hook_authority_check = match transfer_hook_authority {
+                Some(tha) => check_scope.generate_check(tha),
+                None => quote! {},
+            };
+
+            let transfer_hook_program_id_check = match transfer_hook_program_id {
+                Some(thpid) => check_scope.generate_check(thpid),
+                None => quote! {},
+            };
+
+            let permanent_delegate_check = match permanent_delegate {
+                Some(pd) => check_scope.generate_check(pd),
+                None => quote! {},
+            };
+
             let system_program_optional_check = check_scope.generate_check(system_program);
             let token_program_optional_check = check_scope.generate_check(&token_program);
             let rent_optional_check = check_scope.generate_check(rent);
@@ -682,23 +745,126 @@ fn generate_constraint_init_group(
                 #rent_optional_check
                 #owner_optional_check
                 #freeze_authority_optional_check
+                #group_pointer_authority_check
+                #group_pointer_group_address_check
+                #group_member_pointer_authority_check
+                #group_member_pointer_member_address_check
+                #metadata_pointer_authority_check
+                #metadata_pointer_metadata_address_check
+                #close_authority_check
+                #transfer_hook_authority_check
+                #transfer_hook_program_id_check
+                #permanent_delegate_check
             };
 
             let payer_optional_check = check_scope.generate_check(payer);
 
+            let mut extensions = vec![];
+            if group_pointer_authority.is_some() || group_pointer_group_address.is_some() {
+                extensions.push(quote! {::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::GroupPointer});
+            }
+
+            if group_member_pointer_authority.is_some()
+                || group_member_pointer_member_address.is_some()
+            {
+                extensions.push(quote! {::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::GroupMemberPointer});
+            }
+
+            if metadata_pointer_authority.is_some() || metadata_pointer_metadata_address.is_some() {
+                extensions.push(quote! {::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::MetadataPointer});
+            }
+
+            if close_authority.is_some() {
+                extensions.push(quote! {::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::MintCloseAuthority});
+            }
+
+            if transfer_hook_authority.is_some() || transfer_hook_program_id.is_some() {
+                extensions.push(quote! {::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::TransferHook});
+            }
+
+            if permanent_delegate.is_some() {
+                extensions.push(quote! {::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::PermanentDelegate});
+            }
+
+            let mint_space = if extensions.is_empty() {
+                quote! { ::anchor_spl::token::Mint::LEN }
+            } else {
+                quote! { ::anchor_spl::token_interface::find_mint_account_size(Some(&vec![#(#extensions),*]))? }
+            };
+
+            let extensions = if extensions.is_empty() {
+                quote! {Option::<&::anchor_spl::token_interface::ExtensionsVec>::None}
+            } else {
+                quote! {Option::<&::anchor_spl::token_interface::ExtensionsVec>::Some(&vec![#(#extensions),*])}
+            };
+
+            let freeze_authority = match freeze_authority {
+                Some(fa) => quote! { Option::<&anchor_lang::prelude::Pubkey>::Some(&#fa.key()) },
+                None => quote! { Option::<&anchor_lang::prelude::Pubkey>::None },
+            };
+
+            let group_pointer_authority = match group_pointer_authority {
+                Some(gpa) => quote! { Option::<anchor_lang::prelude::Pubkey>::Some(#gpa.key()) },
+                None => quote! { Option::<anchor_lang::prelude::Pubkey>::None },
+            };
+
+            let group_pointer_group_address = match group_pointer_group_address {
+                Some(gpga) => quote! { Option::<anchor_lang::prelude::Pubkey>::Some(#gpga.key()) },
+                None => quote! { Option::<anchor_lang::prelude::Pubkey>::None },
+            };
+
+            let group_member_pointer_authority = match group_member_pointer_authority {
+                Some(gmpa) => quote! { Option::<anchor_lang::prelude::Pubkey>::Some(#gmpa.key()) },
+                None => quote! { Option::<anchor_lang::prelude::Pubkey>::None },
+            };
+
+            let group_member_pointer_member_address = match group_member_pointer_member_address {
+                Some(gmpma) => {
+                    quote! { Option::<anchor_lang::prelude::Pubkey>::Some(#gmpma.key()) }
+                }
+                None => quote! { Option::<anchor_lang::prelude::Pubkey>::None },
+            };
+
+            let metadata_pointer_authority = match metadata_pointer_authority {
+                Some(mpa) => quote! { Option::<anchor_lang::prelude::Pubkey>::Some(#mpa.key()) },
+                None => quote! { Option::<anchor_lang::prelude::Pubkey>::None },
+            };
+
+            let metadata_pointer_metadata_address = match metadata_pointer_metadata_address {
+                Some(mpma) => quote! { Option::<anchor_lang::prelude::Pubkey>::Some(#mpma.key()) },
+                None => quote! { Option::<anchor_lang::prelude::Pubkey>::None },
+            };
+
+            let close_authority = match close_authority {
+                Some(ca) => quote! { Option::<&anchor_lang::prelude::Pubkey>::Some(&#ca.key()) },
+                None => quote! { Option::<&anchor_lang::prelude::Pubkey>::None },
+            };
+
+            let permanent_delegate = match permanent_delegate {
+                Some(pd) => quote! { Option::<&anchor_lang::prelude::Pubkey>::Some(&#pd.key()) },
+                None => quote! { Option::<&anchor_lang::prelude::Pubkey>::None },
+            };
+
+            let transfer_hook_authority = match transfer_hook_authority {
+                Some(tha) => quote! { Option::<anchor_lang::prelude::Pubkey>::Some(#tha.key()) },
+                None => quote! { Option::<anchor_lang::prelude::Pubkey>::None },
+            };
+
+            let transfer_hook_program_id = match transfer_hook_program_id {
+                Some(thpid) => {
+                    quote! { Option::<anchor_lang::prelude::Pubkey>::Some(#thpid.key()) }
+                }
+                None => quote! { Option::<anchor_lang::prelude::Pubkey>::None },
+            };
+
             let create_account = generate_create_account(
                 field,
-                quote! {::anchor_spl::token::Mint::LEN},
+                mint_space,
                 quote! {&#token_program.key()},
                 quote! {#payer},
                 seeds_with_bump,
             );
 
-            let freeze_authority = match freeze_authority {
-                Some(fa) => quote! { Option::<&anchor_lang::prelude::Pubkey>::Some(&#fa.key()) },
-                None => quote! { Option::<&anchor_lang::prelude::Pubkey>::None },
-            };
-
             quote! {
                 // Define the bump and pda variable.
                 #find_pda
@@ -715,6 +881,78 @@ fn generate_constraint_init_group(
                         // Create the account with the system program.
                         #create_account
 
+                        // Initialize extensions.
+                        if let Some(extensions) = #extensions {
+                            for e in extensions {
+                                match e {
+                                    ::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::GroupPointer => {
+                                        let cpi_program = #token_program.to_account_info();
+                                        let accounts = ::anchor_spl::token_interface::GroupPointerInitialize {
+                                            token_program_id: #token_program.to_account_info(),
+                                            mint: #field.to_account_info(),
+                                        };
+                                        let cpi_ctx = anchor_lang::context::CpiContext::new(cpi_program, accounts);
+                                        ::anchor_spl::token_interface::group_pointer_initialize(cpi_ctx, #group_pointer_authority, #group_pointer_group_address)?;
+                                    },
+                                    ::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::GroupMemberPointer => {
+                                        let cpi_program = #token_program.to_account_info();
+                                        let accounts = ::anchor_spl::token_interface::GroupMemberPointerInitialize {
+                                            token_program_id: #token_program.to_account_info(),
+                                            mint: #field.to_account_info(),
+                                        };
+                                        let cpi_ctx = anchor_lang::context::CpiContext::new(cpi_program, accounts);
+                                        ::anchor_spl::token_interface::group_member_pointer_initialize(cpi_ctx, #group_member_pointer_authority, #group_member_pointer_member_address)?;
+                                    },
+                                    ::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::MetadataPointer => {
+                                        let cpi_program = #token_program.to_account_info();
+                                        let accounts = ::anchor_spl::token_interface::MetadataPointerInitialize {
+                                            token_program_id: #token_program.to_account_info(),
+                                            mint: #field.to_account_info(),
+                                        };
+                                        let cpi_ctx = anchor_lang::context::CpiContext::new(cpi_program, accounts);
+                                        ::anchor_spl::token_interface::metadata_pointer_initialize(cpi_ctx, #metadata_pointer_authority, #metadata_pointer_metadata_address)?;
+                                    },
+                                    ::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::MintCloseAuthority => {
+                                        let cpi_program = #token_program.to_account_info();
+                                        let accounts = ::anchor_spl::token_interface::MintCloseAuthorityInitialize {
+                                            token_program_id: #token_program.to_account_info(),
+                                            mint: #field.to_account_info(),
+                                        };
+                                        let cpi_ctx = anchor_lang::context::CpiContext::new(cpi_program, accounts);
+                                        ::anchor_spl::token_interface::mint_close_authority_initialize(cpi_ctx, #close_authority)?;
+                                    },
+                                    ::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::TransferHook => {
+                                        let cpi_program = #token_program.to_account_info();
+                                        let accounts = ::anchor_spl::token_interface::TransferHookInitialize {
+                                            token_program_id: #token_program.to_account_info(),
+                                            mint: #field.to_account_info(),
+                                        };
+                                        let cpi_ctx = anchor_lang::context::CpiContext::new(cpi_program, accounts);
+                                        ::anchor_spl::token_interface::transfer_hook_initialize(cpi_ctx, #transfer_hook_authority, #transfer_hook_program_id)?;
+                                    },
+                                    ::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::NonTransferable => {
+                                        let cpi_program = #token_program.to_account_info();
+                                        let accounts = ::anchor_spl::token_interface::NonTransferableMintInitialize {
+                                            token_program_id: #token_program.to_account_info(),
+                                            mint: #field.to_account_info(),
+                                        };
+                                        let cpi_ctx = anchor_lang::context::CpiContext::new(cpi_program, accounts);
+                                        ::anchor_spl::token_interface::non_transferable_mint_initialize(cpi_ctx)?;
+                                    },
+                                    ::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::PermanentDelegate => {
+                                        let cpi_program = #token_program.to_account_info();
+                                        let accounts = ::anchor_spl::token_interface::PermanentDelegateInitialize {
+                                            token_program_id: #token_program.to_account_info(),
+                                            mint: #field.to_account_info(),
+                                        };
+                                        let cpi_ctx = anchor_lang::context::CpiContext::new(cpi_program, accounts);
+                                        ::anchor_spl::token_interface::permanent_delegate_initialize(cpi_ctx, #permanent_delegate.unwrap())?;
+                                    },
+                                    _ => {} // do nothing
+                                }
+                            };
+                        }
+
                         // Initialize the mint account.
                         let cpi_program = #token_program.to_account_info();
                         let accounts = ::anchor_spl::token_interface::InitializeMint2 {
@@ -723,6 +961,7 @@ fn generate_constraint_init_group(
                         let cpi_ctx = anchor_lang::context::CpiContext::new(cpi_program, accounts);
                         ::anchor_spl::token_interface::initialize_mint2(cpi_ctx, #decimals, &#owner.key(), #freeze_authority)?;
                     }
+
                     let pa: #ty_decl = #from_account_info_unchecked;
                     if #if_needed {
                         if pa.mint_authority != anchor_lang::solana_program::program_option::COption::Some(#owner.key()) {
@@ -1068,12 +1307,203 @@ fn generate_constraint_mint(
         }
         None => quote! {},
     };
+
+    let group_pointer_authority_check = match &c.group_pointer_authority {
+        Some(group_pointer_authority) => {
+            let group_pointer_authority_optional_check =
+                optional_check_scope.generate_check(group_pointer_authority);
+            quote! {
+                let group_pointer = ::anchor_spl::token_interface::get_mint_extension_data::<::anchor_spl::token_interface::spl_token_2022::extension::group_pointer::GroupPointer>(#account_ref);
+                if group_pointer.is_err() {
+                    return Err(anchor_lang::error::ErrorCode::ConstraintMintGroupPointerExtension.into());
+                }
+                #group_pointer_authority_optional_check
+                if group_pointer.unwrap().authority != ::anchor_spl::token_2022_extensions::spl_pod::optional_keys::OptionalNonZeroPubkey::try_from(Some(#group_pointer_authority.key()))? {
+                    return Err(anchor_lang::error::ErrorCode::ConstraintMintGroupPointerExtensionAuthority.into());
+                }
+            }
+        }
+        None => quote! {},
+    };
+
+    let group_pointer_group_address_check = match &c.group_pointer_group_address {
+        Some(group_pointer_group_address) => {
+            let group_pointer_group_address_optional_check =
+                optional_check_scope.generate_check(group_pointer_group_address);
+            quote! {
+                let group_pointer = ::anchor_spl::token_interface::get_mint_extension_data::<::anchor_spl::token_interface::spl_token_2022::extension::group_pointer::GroupPointer>(#account_ref);
+                if group_pointer.is_err() {
+                    return Err(anchor_lang::error::ErrorCode::ConstraintMintGroupPointerExtension.into());
+                }
+                #group_pointer_group_address_optional_check
+                if group_pointer.unwrap().group_address != ::anchor_spl::token_2022_extensions::spl_pod::optional_keys::OptionalNonZeroPubkey::try_from(Some(#group_pointer_group_address.key()))? {
+                    return Err(anchor_lang::error::ErrorCode::ConstraintMintGroupPointerExtensionGroupAddress.into());
+                }
+            }
+        }
+        None => quote! {},
+    };
+
+    let group_member_pointer_authority_check = match &c.group_member_pointer_authority {
+        Some(group_member_pointer_authority) => {
+            let group_member_pointer_authority_optional_check =
+                optional_check_scope.generate_check(group_member_pointer_authority);
+            quote! {
+                let group_member_pointer = ::anchor_spl::token_interface::get_mint_extension_data::<::anchor_spl::token_interface::spl_token_2022::extension::group_member_pointer::GroupMemberPointer>(#account_ref);
+                if group_member_pointer.is_err() {
+                    return Err(anchor_lang::error::ErrorCode::ConstraintMintGroupMemberPointerExtension.into());
+                }
+                #group_member_pointer_authority_optional_check
+                if group_member_pointer.unwrap().authority != ::anchor_spl::token_2022_extensions::spl_pod::optional_keys::OptionalNonZeroPubkey::try_from(Some(#group_member_pointer_authority.key()))? {
+                    return Err(anchor_lang::error::ErrorCode::ConstraintMintGroupMemberPointerExtensionAuthority.into());
+                }
+            }
+        }
+        None => quote! {},
+    };
+
+    let group_member_pointer_member_address_check = match &c.group_member_pointer_member_address {
+        Some(group_member_pointer_member_address) => {
+            let group_member_pointer_member_address_optional_check =
+                optional_check_scope.generate_check(group_member_pointer_member_address);
+            quote! {
+                let group_member_pointer = ::anchor_spl::token_interface::get_mint_extension_data::<::anchor_spl::token_interface::spl_token_2022::extension::group_member_pointer::GroupMemberPointer>(#account_ref);
+                if group_member_pointer.is_err() {
+                    return Err(anchor_lang::error::ErrorCode::ConstraintMintGroupMemberPointerExtension.into());
+                }
+                #group_member_pointer_member_address_optional_check
+                if group_member_pointer.unwrap().member_address != ::anchor_spl::token_2022_extensions::spl_pod::optional_keys::OptionalNonZeroPubkey::try_from(Some(#group_member_pointer_member_address.key()))? {
+                    return Err(anchor_lang::error::ErrorCode::ConstraintMintGroupMemberPointerExtensionMemberAddress.into());
+                }
+            }
+        }
+        None => quote! {},
+    };
+
+    let metadata_pointer_authority_check = match &c.metadata_pointer_authority {
+        Some(metadata_pointer_authority) => {
+            let metadata_pointer_authority_optional_check =
+                optional_check_scope.generate_check(metadata_pointer_authority);
+            quote! {
+                let metadata_pointer = ::anchor_spl::token_interface::get_mint_extension_data::<::anchor_spl::token_interface::spl_token_2022::extension::metadata_pointer::MetadataPointer>(#account_ref);
+                if metadata_pointer.is_err() {
+                    return Err(anchor_lang::error::ErrorCode::ConstraintMintMetadataPointerExtension.into());
+                }
+                #metadata_pointer_authority_optional_check
+                if metadata_pointer.unwrap().authority != ::anchor_spl::token_2022_extensions::spl_pod::optional_keys::OptionalNonZeroPubkey::try_from(Some(#metadata_pointer_authority.key()))? {
+                    return Err(anchor_lang::error::ErrorCode::ConstraintMintMetadataPointerExtensionAuthority.into());
+                }
+            }
+        }
+        None => quote! {},
+    };
+
+    let metadata_pointer_metadata_address_check = match &c.metadata_pointer_metadata_address {
+        Some(metadata_pointer_metadata_address) => {
+            let metadata_pointer_metadata_address_optional_check =
+                optional_check_scope.generate_check(metadata_pointer_metadata_address);
+            quote! {
+                let metadata_pointer = ::anchor_spl::token_interface::get_mint_extension_data::<::anchor_spl::token_interface::spl_token_2022::extension::metadata_pointer::MetadataPointer>(#account_ref);
+                if metadata_pointer.is_err() {
+                    return Err(anchor_lang::error::ErrorCode::ConstraintMintMetadataPointerExtension.into());
+                }
+                #metadata_pointer_metadata_address_optional_check
+                if metadata_pointer.unwrap().metadata_address != ::anchor_spl::token_2022_extensions::spl_pod::optional_keys::OptionalNonZeroPubkey::try_from(Some(#metadata_pointer_metadata_address.key()))? {
+                    return Err(anchor_lang::error::ErrorCode::ConstraintMintMetadataPointerExtensionMetadataAddress.into());
+                }
+            }
+        }
+        None => quote! {},
+    };
+
+    let close_authority_check = match &c.close_authority {
+        Some(close_authority) => {
+            let close_authority_optional_check =
+                optional_check_scope.generate_check(close_authority);
+            quote! {
+                let close_authority = ::anchor_spl::token_interface::get_mint_extension_data::<::anchor_spl::token_interface::spl_token_2022::extension::mint_close_authority::MintCloseAuthority>(#account_ref);
+                if close_authority.is_err() {
+                    return Err(anchor_lang::error::ErrorCode::ConstraintMintCloseAuthorityExtension.into());
+                }
+                #close_authority_optional_check
+                if close_authority.unwrap().close_authority != ::anchor_spl::token_2022_extensions::spl_pod::optional_keys::OptionalNonZeroPubkey::try_from(Some(#close_authority.key()))? {
+                    return Err(anchor_lang::error::ErrorCode::ConstraintMintCloseAuthorityExtensionAuthority.into());
+                }
+            }
+        }
+        None => quote! {},
+    };
+
+    let permanent_delegate_check = match &c.permanent_delegate {
+        Some(permanent_delegate) => {
+            let permanent_delegate_optional_check =
+                optional_check_scope.generate_check(permanent_delegate);
+            quote! {
+                let permanent_delegate = ::anchor_spl::token_interface::get_mint_extension_data::<::anchor_spl::token_interface::spl_token_2022::extension::permanent_delegate::PermanentDelegate>(#account_ref);
+                if permanent_delegate.is_err() {
+                    return Err(anchor_lang::error::ErrorCode::ConstraintMintPermanentDelegateExtension.into());
+                }
+                #permanent_delegate_optional_check
+                if permanent_delegate.unwrap().delegate != ::anchor_spl::token_2022_extensions::spl_pod::optional_keys::OptionalNonZeroPubkey::try_from(Some(#permanent_delegate.key()))? {
+                    return Err(anchor_lang::error::ErrorCode::ConstraintMintPermanentDelegateExtensionDelegate.into());
+                }
+            }
+        }
+        None => quote! {},
+    };
+
+    let transfer_hook_authority_check = match &c.transfer_hook_authority {
+        Some(transfer_hook_authority) => {
+            let transfer_hook_authority_optional_check =
+                optional_check_scope.generate_check(transfer_hook_authority);
+            quote! {
+                let transfer_hook = ::anchor_spl::token_interface::get_mint_extension_data::<::anchor_spl::token_interface::spl_token_2022::extension::transfer_hook::TransferHook>(#account_ref);
+                if transfer_hook.is_err() {
+                    return Err(anchor_lang::error::ErrorCode::ConstraintMintTransferHookExtension.into());
+                }
+                #transfer_hook_authority_optional_check
+                if transfer_hook.unwrap().authority != ::anchor_spl::token_2022_extensions::spl_pod::optional_keys::OptionalNonZeroPubkey::try_from(Some(#transfer_hook_authority.key()))? {
+                    return Err(anchor_lang::error::ErrorCode::ConstraintMintTransferHookExtensionAuthority.into());
+                }
+            }
+        }
+        None => quote! {},
+    };
+
+    let transfer_hook_program_id_check = match &c.transfer_hook_program_id {
+        Some(transfer_hook_program_id) => {
+            let transfer_hook_program_id_optional_check =
+                optional_check_scope.generate_check(transfer_hook_program_id);
+            quote! {
+                let transfer_hook = ::anchor_spl::token_interface::get_mint_extension_data::<::anchor_spl::token_interface::spl_token_2022::extension::transfer_hook::TransferHook>(#account_ref);
+                if transfer_hook.is_err() {
+                    return Err(anchor_lang::error::ErrorCode::ConstraintMintTransferHookExtension.into());
+                }
+                #transfer_hook_program_id_optional_check
+                if transfer_hook.unwrap().program_id != ::anchor_spl::token_2022_extensions::spl_pod::optional_keys::OptionalNonZeroPubkey::try_from(Some(#transfer_hook_program_id.key()))? {
+                    return Err(anchor_lang::error::ErrorCode::ConstraintMintTransferHookExtensionProgramId.into());
+                }
+            }
+        }
+        None => quote! {},
+    };
+
     quote! {
         {
             #decimal_check
             #mint_authority_check
             #freeze_authority_check
             #token_program_check
+            #group_pointer_authority_check
+            #group_pointer_group_address_check
+            #group_member_pointer_authority_check
+            #group_member_pointer_member_address_check
+            #metadata_pointer_authority_check
+            #metadata_pointer_metadata_address_check
+            #close_authority_check
+            #permanent_delegate_check
+            #transfer_hook_authority_check
+            #transfer_hook_program_id_check
         }
     }
 }

+ 107 - 0
lang/syn/src/lib.rs

@@ -680,6 +680,21 @@ pub enum ConstraintToken {
     Realloc(Context<ConstraintRealloc>),
     ReallocPayer(Context<ConstraintReallocPayer>),
     ReallocZero(Context<ConstraintReallocZero>),
+    // extensions
+    ExtensionGroupPointerAuthority(Context<ConstraintExtensionAuthority>),
+    ExtensionGroupPointerGroupAddress(Context<ConstraintExtensionGroupPointerGroupAddress>),
+    ExtensionGroupMemberPointerAuthority(Context<ConstraintExtensionAuthority>),
+    ExtensionGroupMemberPointerMemberAddress(
+        Context<ConstraintExtensionGroupMemberPointerMemberAddress>,
+    ),
+    ExtensionMetadataPointerAuthority(Context<ConstraintExtensionAuthority>),
+    ExtensionMetadataPointerMetadataAddress(
+        Context<ConstraintExtensionMetadataPointerMetadataAddress>,
+    ),
+    ExtensionCloseAuthority(Context<ConstraintExtensionAuthority>),
+    ExtensionTokenHookAuthority(Context<ConstraintExtensionAuthority>),
+    ExtensionTokenHookProgramId(Context<ConstraintExtensionTokenHookProgramId>),
+    ExtensionPermanentDelegate(Context<ConstraintExtensionPermanentDelegate>),
 }
 
 impl Parse for ConstraintToken {
@@ -796,6 +811,37 @@ pub struct ConstraintSpace {
     pub space: Expr,
 }
 
+// extension constraints
+#[derive(Debug, Clone)]
+pub struct ConstraintExtensionAuthority {
+    pub authority: Expr,
+}
+
+#[derive(Debug, Clone)]
+pub struct ConstraintExtensionGroupPointerGroupAddress {
+    pub group_address: Expr,
+}
+
+#[derive(Debug, Clone)]
+pub struct ConstraintExtensionGroupMemberPointerMemberAddress {
+    pub member_address: Expr,
+}
+
+#[derive(Debug, Clone)]
+pub struct ConstraintExtensionMetadataPointerMetadataAddress {
+    pub metadata_address: Expr,
+}
+
+#[derive(Debug, Clone)]
+pub struct ConstraintExtensionTokenHookProgramId {
+    pub program_id: Expr,
+}
+
+#[derive(Debug, Clone)]
+pub struct ConstraintExtensionPermanentDelegate {
+    pub permanent_delegate: Expr,
+}
+
 #[derive(Debug, Clone)]
 #[allow(clippy::large_enum_variant)]
 pub enum InitKind {
@@ -822,6 +868,17 @@ pub enum InitKind {
         freeze_authority: Option<Expr>,
         decimals: Expr,
         token_program: Option<Expr>,
+        // extensions
+        group_pointer_authority: Option<Expr>,
+        group_pointer_group_address: Option<Expr>,
+        group_member_pointer_authority: Option<Expr>,
+        group_member_pointer_member_address: Option<Expr>,
+        metadata_pointer_authority: Option<Expr>,
+        metadata_pointer_metadata_address: Option<Expr>,
+        close_authority: Option<Expr>,
+        permanent_delegate: Option<Expr>,
+        transfer_hook_authority: Option<Expr>,
+        transfer_hook_program_id: Option<Expr>,
     },
 }
 
@@ -835,6 +892,46 @@ pub struct ConstraintTokenMint {
     pub mint: Expr,
 }
 
+#[derive(Debug, Clone)]
+pub struct ConstraintMintConfidentialTransferData {
+    pub confidential_transfer_data: Expr,
+}
+
+#[derive(Debug, Clone)]
+pub struct ConstraintMintMetadata {
+    pub token_metadata: Expr,
+}
+
+#[derive(Debug, Clone)]
+pub struct ConstraintMintTokenGroupData {
+    pub token_group_data: Expr,
+}
+
+#[derive(Debug, Clone)]
+pub struct ConstraintMintTokenGroupMemberData {
+    pub token_group_member_data: Expr,
+}
+
+#[derive(Debug, Clone)]
+pub struct ConstraintMintMetadataPointerData {
+    pub metadata_pointer_data: Expr,
+}
+
+#[derive(Debug, Clone)]
+pub struct ConstraintMintGroupPointerData {
+    pub group_pointer_data: Expr,
+}
+
+#[derive(Debug, Clone)]
+pub struct ConstraintMintGroupMemberPointerData {
+    pub group_member_pointer_data: Expr,
+}
+
+#[derive(Debug, Clone)]
+pub struct ConstraintMintCloseAuthority {
+    pub close_authority: Expr,
+}
+
 #[derive(Debug, Clone)]
 pub struct ConstraintTokenAuthority {
     pub auth: Expr,
@@ -890,6 +987,16 @@ pub struct ConstraintTokenMintGroup {
     pub mint_authority: Option<Expr>,
     pub freeze_authority: Option<Expr>,
     pub token_program: Option<Expr>,
+    pub group_pointer_authority: Option<Expr>,
+    pub group_pointer_group_address: Option<Expr>,
+    pub group_member_pointer_authority: Option<Expr>,
+    pub group_member_pointer_member_address: Option<Expr>,
+    pub metadata_pointer_authority: Option<Expr>,
+    pub metadata_pointer_metadata_address: Option<Expr>,
+    pub close_authority: Option<Expr>,
+    pub permanent_delegate: Option<Expr>,
+    pub transfer_hook_authority: Option<Expr>,
+    pub transfer_hook_program_id: Option<Expr>,
 }
 
 // Syntaxt context object for preserving metadata about the inner item.

+ 441 - 1
lang/syn/src/parser/accounts/constraints.rs

@@ -89,6 +89,177 @@ pub fn parse_token(stream: ParseStream) -> ParseResult<ConstraintToken> {
                 _ => return Err(ParseError::new(ident.span(), "Invalid attribute")),
             }
         }
+        "extensions" => {
+            stream.parse::<Token![:]>()?;
+            stream.parse::<Token![:]>()?;
+            let kw = stream.call(Ident::parse_any)?.to_string();
+
+            match kw.as_str() {
+                "group_pointer" => {
+                    stream.parse::<Token![:]>()?;
+                    stream.parse::<Token![:]>()?;
+                    let kw = stream.call(Ident::parse_any)?.to_string();
+                    stream.parse::<Token![=]>()?;
+
+                    let span = ident
+                        .span()
+                        .join(stream.span())
+                        .unwrap_or_else(|| ident.span());
+
+                    match kw.as_str() {
+                        "authority" => {
+                            ConstraintToken::ExtensionGroupPointerAuthority(Context::new(
+                                span,
+                                ConstraintExtensionAuthority {
+                                    authority: stream.parse()?,
+                                },
+                            ))
+                        }
+                        "group_address" => {
+                            ConstraintToken::ExtensionGroupPointerGroupAddress(Context::new(
+                                span,
+                                ConstraintExtensionGroupPointerGroupAddress {
+                                    group_address: stream.parse()?,
+                                },
+                            ))
+                        }
+                        _ => return Err(ParseError::new(ident.span(), "Invalid attribute")),
+                    }
+                }
+                "group_member_pointer" => {
+                    stream.parse::<Token![:]>()?;
+                    stream.parse::<Token![:]>()?;
+                    let kw = stream.call(Ident::parse_any)?.to_string();
+                    stream.parse::<Token![=]>()?;
+
+                    let span = ident
+                        .span()
+                        .join(stream.span())
+                        .unwrap_or_else(|| ident.span());
+
+                    match kw.as_str() {
+                        "authority" => {
+                            ConstraintToken::ExtensionGroupMemberPointerAuthority(Context::new(
+                                span,
+                                ConstraintExtensionAuthority {
+                                    authority: stream.parse()?,
+                                },
+                            ))
+                        }
+                        "member_address" => {
+                            ConstraintToken::ExtensionGroupMemberPointerMemberAddress(Context::new(
+                                span,
+                                ConstraintExtensionGroupMemberPointerMemberAddress {
+                                    member_address: stream.parse()?,
+                                },
+                            ))
+                        }
+                        _ => return Err(ParseError::new(ident.span(), "Invalid attribute")),
+                    }
+                }
+                "metadata_pointer" => {
+                    stream.parse::<Token![:]>()?;
+                    stream.parse::<Token![:]>()?;
+                    let kw = stream.call(Ident::parse_any)?.to_string();
+                    stream.parse::<Token![=]>()?;
+
+                    let span = ident
+                        .span()
+                        .join(stream.span())
+                        .unwrap_or_else(|| ident.span());
+
+                    match kw.as_str() {
+                        "authority" => {
+                            ConstraintToken::ExtensionMetadataPointerAuthority(Context::new(
+                                span,
+                                ConstraintExtensionAuthority {
+                                    authority: stream.parse()?,
+                                },
+                            ))
+                        }
+                        "metadata_address" => {
+                            ConstraintToken::ExtensionMetadataPointerMetadataAddress(Context::new(
+                                span,
+                                ConstraintExtensionMetadataPointerMetadataAddress {
+                                    metadata_address: stream.parse()?,
+                                },
+                            ))
+                        }
+                        _ => return Err(ParseError::new(ident.span(), "Invalid attribute")),
+                    }
+                }
+                "close_authority" => {
+                    stream.parse::<Token![:]>()?;
+                    stream.parse::<Token![:]>()?;
+                    let kw = stream.call(Ident::parse_any)?.to_string();
+                    stream.parse::<Token![=]>()?;
+
+                    let span = ident
+                        .span()
+                        .join(stream.span())
+                        .unwrap_or_else(|| ident.span());
+
+                    match kw.as_str() {
+                        "authority" => ConstraintToken::ExtensionCloseAuthority(Context::new(
+                            span,
+                            ConstraintExtensionAuthority {
+                                authority: stream.parse()?,
+                            },
+                        )),
+                        _ => return Err(ParseError::new(ident.span(), "Invalid attribute")),
+                    }
+                }
+                "permanent_delegate" => {
+                    stream.parse::<Token![:]>()?;
+                    stream.parse::<Token![:]>()?;
+                    let kw = stream.call(Ident::parse_any)?.to_string();
+                    stream.parse::<Token![=]>()?;
+
+                    let span = ident
+                        .span()
+                        .join(stream.span())
+                        .unwrap_or_else(|| ident.span());
+
+                    match kw.as_str() {
+                        "delegate" => ConstraintToken::ExtensionPermanentDelegate(Context::new(
+                            span,
+                            ConstraintExtensionPermanentDelegate {
+                                permanent_delegate: stream.parse()?,
+                            },
+                        )),
+                        _ => return Err(ParseError::new(ident.span(), "Invalid attribute")),
+                    }
+                }
+                "transfer_hook" => {
+                    stream.parse::<Token![:]>()?;
+                    stream.parse::<Token![:]>()?;
+                    let kw = stream.call(Ident::parse_any)?.to_string();
+                    stream.parse::<Token![=]>()?;
+
+                    let span = ident
+                        .span()
+                        .join(stream.span())
+                        .unwrap_or_else(|| ident.span());
+
+                    match kw.as_str() {
+                        "authority" => ConstraintToken::ExtensionTokenHookAuthority(Context::new(
+                            span,
+                            ConstraintExtensionAuthority {
+                                authority: stream.parse()?,
+                            },
+                        )),
+                        "program_id" => ConstraintToken::ExtensionTokenHookProgramId(Context::new(
+                            span,
+                            ConstraintExtensionTokenHookProgramId {
+                                program_id: stream.parse()?,
+                            },
+                        )),
+                        _ => return Err(ParseError::new(ident.span(), "Invalid attribute")),
+                    }
+                }
+                _ => return Err(ParseError::new(ident.span(), "Invalid attribute")),
+            }
+        }
         "token" => {
             stream.parse::<Token![:]>()?;
             stream.parse::<Token![:]>()?;
@@ -354,6 +525,19 @@ pub struct ConstraintGroupBuilder<'ty> {
     pub mint_freeze_authority: Option<Context<ConstraintMintFreezeAuthority>>,
     pub mint_decimals: Option<Context<ConstraintMintDecimals>>,
     pub mint_token_program: Option<Context<ConstraintTokenProgram>>,
+    pub extension_group_pointer_authority: Option<Context<ConstraintExtensionAuthority>>,
+    pub extension_group_pointer_group_address:
+        Option<Context<ConstraintExtensionGroupPointerGroupAddress>>,
+    pub extension_group_member_pointer_authority: Option<Context<ConstraintExtensionAuthority>>,
+    pub extension_group_member_pointer_member_address:
+        Option<Context<ConstraintExtensionGroupMemberPointerMemberAddress>>,
+    pub extension_metadata_pointer_authority: Option<Context<ConstraintExtensionAuthority>>,
+    pub extension_metadata_pointer_metadata_address:
+        Option<Context<ConstraintExtensionMetadataPointerMetadataAddress>>,
+    pub extension_close_authority: Option<Context<ConstraintExtensionAuthority>>,
+    pub extension_transfer_hook_authority: Option<Context<ConstraintExtensionAuthority>>,
+    pub extension_transfer_hook_program_id: Option<Context<ConstraintExtensionTokenHookProgramId>>,
+    pub extension_permanent_delegate: Option<Context<ConstraintExtensionPermanentDelegate>>,
     pub bump: Option<Context<ConstraintTokenBump>>,
     pub program_seed: Option<Context<ConstraintProgramSeed>>,
     pub realloc: Option<Context<ConstraintRealloc>>,
@@ -389,6 +573,16 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
             mint_freeze_authority: None,
             mint_decimals: None,
             mint_token_program: None,
+            extension_group_pointer_authority: None,
+            extension_group_pointer_group_address: None,
+            extension_group_member_pointer_authority: None,
+            extension_group_member_pointer_member_address: None,
+            extension_metadata_pointer_authority: None,
+            extension_metadata_pointer_metadata_address: None,
+            extension_close_authority: None,
+            extension_transfer_hook_authority: None,
+            extension_transfer_hook_program_id: None,
+            extension_permanent_delegate: None,
             bump: None,
             program_seed: None,
             realloc: None,
@@ -591,6 +785,16 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
             mint_freeze_authority,
             mint_decimals,
             mint_token_program,
+            extension_group_pointer_authority,
+            extension_group_pointer_group_address,
+            extension_group_member_pointer_authority,
+            extension_group_member_pointer_member_address,
+            extension_metadata_pointer_authority,
+            extension_metadata_pointer_metadata_address,
+            extension_close_authority,
+            extension_transfer_hook_authority,
+            extension_transfer_hook_program_id,
+            extension_permanent_delegate,
             bump,
             program_seed,
             realloc,
@@ -680,8 +884,33 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
             &mint_authority,
             &mint_freeze_authority,
             &mint_token_program,
+            &extension_group_pointer_authority,
+            &extension_group_pointer_group_address,
+            &extension_group_member_pointer_authority,
+            &extension_group_member_pointer_member_address,
+            &extension_metadata_pointer_authority,
+            &extension_metadata_pointer_metadata_address,
+            &extension_close_authority,
+            &extension_transfer_hook_authority,
+            &extension_transfer_hook_program_id,
+            &extension_permanent_delegate,
         ) {
-            (None, None, None, None) => None,
+            (
+                None,
+                None,
+                None,
+                None,
+                None,
+                None,
+                None,
+                None,
+                None,
+                None,
+                None,
+                None,
+                None,
+                None,
+            ) => None,
             _ => Some(ConstraintTokenMintGroup {
                 decimals: mint_decimals
                     .as_ref()
@@ -695,6 +924,37 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
                 token_program: mint_token_program
                     .as_ref()
                     .map(|a| a.clone().into_inner().token_program),
+                // extensions
+                group_pointer_authority: extension_group_pointer_authority
+                    .as_ref()
+                    .map(|a| a.clone().into_inner().authority),
+                group_pointer_group_address: extension_group_pointer_group_address
+                    .as_ref()
+                    .map(|a| a.clone().into_inner().group_address),
+                group_member_pointer_authority: extension_group_member_pointer_authority
+                    .as_ref()
+                    .map(|a| a.clone().into_inner().authority),
+                group_member_pointer_member_address: extension_group_member_pointer_member_address
+                    .as_ref()
+                    .map(|a| a.clone().into_inner().member_address),
+                metadata_pointer_authority: extension_metadata_pointer_authority
+                    .as_ref()
+                    .map(|a| a.clone().into_inner().authority),
+                metadata_pointer_metadata_address: extension_metadata_pointer_metadata_address
+                    .as_ref()
+                    .map(|a| a.clone().into_inner().metadata_address),
+                close_authority: extension_close_authority
+                    .as_ref()
+                    .map(|a| a.clone().into_inner().authority),
+                permanent_delegate: extension_permanent_delegate
+                    .as_ref()
+                    .map(|a| a.clone().into_inner().permanent_delegate),
+                transfer_hook_authority: extension_transfer_hook_authority
+                    .as_ref()
+                    .map(|a| a.clone().into_inner().authority),
+                transfer_hook_program_id: extension_transfer_hook_program_id
+                    .as_ref()
+                    .map(|a| a.clone().into_inner().program_id),
             }),
         };
 
@@ -734,6 +994,17 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
                         },
                         freeze_authority: mint_freeze_authority.map(|fa| fa.into_inner().mint_freeze_auth),
                         token_program: mint_token_program.map(|tp| tp.into_inner().token_program),
+                        // extensions
+                        group_pointer_authority: extension_group_pointer_authority.map(|gpa| gpa.into_inner().authority),
+                        group_pointer_group_address: extension_group_pointer_group_address.map(|gpga| gpga.into_inner().group_address),
+                        group_member_pointer_authority: extension_group_member_pointer_authority.map(|gmpa| gmpa.into_inner().authority),
+                        group_member_pointer_member_address: extension_group_member_pointer_member_address.map(|gmpma| gmpma.into_inner().member_address),
+                        metadata_pointer_authority: extension_metadata_pointer_authority.map(|mpa| mpa.into_inner().authority),
+                        metadata_pointer_metadata_address: extension_metadata_pointer_metadata_address.map(|mpma| mpma.into_inner().metadata_address),
+                        close_authority: extension_close_authority.map(|ca| ca.into_inner().authority),
+                        permanent_delegate: extension_permanent_delegate.map(|pd| pd.into_inner().permanent_delegate),
+                        transfer_hook_authority: extension_transfer_hook_authority.map(|tha| tha.into_inner().authority),
+                        transfer_hook_program_id: extension_transfer_hook_program_id.map(|thpid| thpid.into_inner().program_id),
                     }
                 } else {
                     InitKind::Program {
@@ -796,6 +1067,32 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
             ConstraintToken::Realloc(c) => self.add_realloc(c),
             ConstraintToken::ReallocPayer(c) => self.add_realloc_payer(c),
             ConstraintToken::ReallocZero(c) => self.add_realloc_zero(c),
+            ConstraintToken::ExtensionGroupPointerAuthority(c) => {
+                self.add_extension_group_pointer_authority(c)
+            }
+            ConstraintToken::ExtensionGroupPointerGroupAddress(c) => {
+                self.add_extension_group_pointer_group_address(c)
+            }
+            ConstraintToken::ExtensionGroupMemberPointerAuthority(c) => {
+                self.add_extension_group_member_pointer_authority(c)
+            }
+            ConstraintToken::ExtensionGroupMemberPointerMemberAddress(c) => {
+                self.add_extension_group_member_pointer_member_address(c)
+            }
+            ConstraintToken::ExtensionMetadataPointerAuthority(c) => {
+                self.add_extension_metadata_pointer_authority(c)
+            }
+            ConstraintToken::ExtensionMetadataPointerMetadataAddress(c) => {
+                self.add_extension_metadata_pointer_metadata_address(c)
+            }
+            ConstraintToken::ExtensionCloseAuthority(c) => self.add_extension_close_authority(c),
+            ConstraintToken::ExtensionTokenHookAuthority(c) => self.add_extension_authority(c),
+            ConstraintToken::ExtensionTokenHookProgramId(c) => {
+                self.add_extension_transfer_hook_program_id(c)
+            }
+            ConstraintToken::ExtensionPermanentDelegate(c) => {
+                self.add_extension_permanent_delegate(c)
+            }
         }
     }
 
@@ -1221,4 +1518,147 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
         self.space.replace(c);
         Ok(())
     }
+
+    // extensions
+
+    fn add_extension_group_pointer_authority(
+        &mut self,
+        c: Context<ConstraintExtensionAuthority>,
+    ) -> ParseResult<()> {
+        if self.extension_group_pointer_authority.is_some() {
+            return Err(ParseError::new(
+                c.span(),
+                "extension group pointer authority already provided",
+            ));
+        }
+        self.extension_group_pointer_authority.replace(c);
+        Ok(())
+    }
+
+    fn add_extension_group_pointer_group_address(
+        &mut self,
+        c: Context<ConstraintExtensionGroupPointerGroupAddress>,
+    ) -> ParseResult<()> {
+        if self.extension_group_pointer_group_address.is_some() {
+            return Err(ParseError::new(
+                c.span(),
+                "extension group pointer group address already provided",
+            ));
+        }
+        self.extension_group_pointer_group_address.replace(c);
+        Ok(())
+    }
+
+    fn add_extension_group_member_pointer_authority(
+        &mut self,
+        c: Context<ConstraintExtensionAuthority>,
+    ) -> ParseResult<()> {
+        if self.extension_group_member_pointer_authority.is_some() {
+            return Err(ParseError::new(
+                c.span(),
+                "extension group member pointer authority already provided",
+            ));
+        }
+        self.extension_group_member_pointer_authority.replace(c);
+        Ok(())
+    }
+
+    fn add_extension_group_member_pointer_member_address(
+        &mut self,
+        c: Context<ConstraintExtensionGroupMemberPointerMemberAddress>,
+    ) -> ParseResult<()> {
+        if self.extension_group_member_pointer_member_address.is_some() {
+            return Err(ParseError::new(
+                c.span(),
+                "extension group member pointer member address already provided",
+            ));
+        }
+        self.extension_group_member_pointer_member_address
+            .replace(c);
+        Ok(())
+    }
+
+    fn add_extension_metadata_pointer_authority(
+        &mut self,
+        c: Context<ConstraintExtensionAuthority>,
+    ) -> ParseResult<()> {
+        if self.extension_metadata_pointer_authority.is_some() {
+            return Err(ParseError::new(
+                c.span(),
+                "extension metadata pointer authority already provided",
+            ));
+        }
+        self.extension_metadata_pointer_authority.replace(c);
+        Ok(())
+    }
+
+    fn add_extension_metadata_pointer_metadata_address(
+        &mut self,
+        c: Context<ConstraintExtensionMetadataPointerMetadataAddress>,
+    ) -> ParseResult<()> {
+        if self.extension_metadata_pointer_metadata_address.is_some() {
+            return Err(ParseError::new(
+                c.span(),
+                "extension metadata pointer metadata address already provided",
+            ));
+        }
+        self.extension_metadata_pointer_metadata_address.replace(c);
+        Ok(())
+    }
+
+    fn add_extension_close_authority(
+        &mut self,
+        c: Context<ConstraintExtensionAuthority>,
+    ) -> ParseResult<()> {
+        if self.extension_close_authority.is_some() {
+            return Err(ParseError::new(
+                c.span(),
+                "extension close authority already provided",
+            ));
+        }
+        self.extension_close_authority.replace(c);
+        Ok(())
+    }
+
+    fn add_extension_authority(
+        &mut self,
+        c: Context<ConstraintExtensionAuthority>,
+    ) -> ParseResult<()> {
+        if self.extension_transfer_hook_authority.is_some() {
+            return Err(ParseError::new(
+                c.span(),
+                "extension transfer hook authority already provided",
+            ));
+        }
+        self.extension_transfer_hook_authority.replace(c);
+        Ok(())
+    }
+
+    fn add_extension_transfer_hook_program_id(
+        &mut self,
+        c: Context<ConstraintExtensionTokenHookProgramId>,
+    ) -> ParseResult<()> {
+        if self.extension_transfer_hook_program_id.is_some() {
+            return Err(ParseError::new(
+                c.span(),
+                "extension transfer hook program id already provided",
+            ));
+        }
+        self.extension_transfer_hook_program_id.replace(c);
+        Ok(())
+    }
+
+    fn add_extension_permanent_delegate(
+        &mut self,
+        c: Context<ConstraintExtensionPermanentDelegate>,
+    ) -> ParseResult<()> {
+        if self.extension_permanent_delegate.is_some() {
+            return Err(ParseError::new(
+                c.span(),
+                "extension permanent delegate already provided",
+            ));
+        }
+        self.extension_permanent_delegate.replace(c);
+        Ok(())
+    }
 }

+ 5 - 1
spl/Cargo.toml

@@ -12,7 +12,7 @@ all-features = true
 rustdoc-args = ["--cfg", "docsrs"]
 
 [features]
-default = ["associated_token", "mint", "token", "token_2022"]
+default = ["associated_token", "mint", "token", "token_2022", "token_2022_extensions"]
 associated_token = ["spl-associated-token-account"]
 dex = ["serum_dex"]
 devnet = []
@@ -24,6 +24,7 @@ mint = []
 stake = ["borsh"]
 token = ["spl-token"]
 token_2022 = ["spl-token-2022"]
+token_2022_extensions = ["spl-token-2022", "spl-token-group-interface", "spl-token-metadata-interface", "spl-pod"]
 
 [dependencies]
 anchor-lang = { path = "../lang", version = "0.29.0", features = ["derive"] }
@@ -35,3 +36,6 @@ spl-associated-token-account = { version = "3", features = ["no-entrypoint"], op
 spl-memo = { version = "4", features = ["no-entrypoint"], optional = true }
 spl-token = { version = "4", features = ["no-entrypoint"], optional = true }
 spl-token-2022 = { version = "3", features = ["no-entrypoint"], optional = true }
+spl-token-group-interface = { version = "0.2.3", optional = true }
+spl-token-metadata-interface = { version = "0.3.3", optional = true }
+spl-pod = { version = "0.2.2", optional = true }

+ 3 - 0
spl/src/lib.rs

@@ -14,6 +14,9 @@ pub mod token;
 #[cfg(feature = "token_2022")]
 pub mod token_2022;
 
+#[cfg(feature = "token_2022_extensions")]
+pub mod token_2022_extensions;
+
 #[cfg(feature = "token_2022")]
 pub mod token_interface;
 

+ 0 - 4
spl/src/token_2022.rs

@@ -522,7 +522,3 @@ impl anchor_lang::Id for Token2022 {
         ID
     }
 }
-
-// Field parsers to save compute. All account validation is assumed to be done
-// outside of these methods.
-pub use crate::token::accessor;

+ 1 - 0
spl/src/token_2022_extensions/confidential_transfer.rs

@@ -0,0 +1 @@
+// waiting for labs to merge

+ 1 - 0
spl/src/token_2022_extensions/confidential_transfer_fee.rs

@@ -0,0 +1 @@
+// waiting for labs to merge

+ 50 - 0
spl/src/token_2022_extensions/cpi_guard.rs

@@ -0,0 +1,50 @@
+use anchor_lang::solana_program::pubkey::Pubkey;
+use anchor_lang::Result;
+use anchor_lang::{context::CpiContext, Accounts};
+use solana_program::account_info::AccountInfo;
+
+pub fn cpi_guard_enable<'info>(ctx: CpiContext<'_, '_, '_, 'info, CpiGuard<'info>>) -> Result<()> {
+    let ix = spl_token_2022::extension::cpi_guard::instruction::enable_cpi_guard(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.account.key,
+        ctx.accounts.account.owner,
+        &[],
+    )?;
+    solana_program::program::invoke_signed(
+        &ix,
+        &[
+            ctx.accounts.token_program_id,
+            ctx.accounts.account,
+            ctx.accounts.owner,
+        ],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+pub fn cpi_guard_disable<'info>(ctx: CpiContext<'_, '_, '_, 'info, CpiGuard<'info>>) -> Result<()> {
+    let ix = spl_token_2022::extension::cpi_guard::instruction::disable_cpi_guard(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.account.key,
+        ctx.accounts.account.owner,
+        &[],
+    )?;
+
+    solana_program::program::invoke_signed(
+        &ix,
+        &[
+            ctx.accounts.token_program_id,
+            ctx.accounts.account,
+            ctx.accounts.owner,
+        ],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct CpiGuard<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub account: AccountInfo<'info>,
+    pub owner: AccountInfo<'info>,
+}

+ 59 - 0
spl/src/token_2022_extensions/default_account_state.rs

@@ -0,0 +1,59 @@
+use anchor_lang::solana_program::pubkey::Pubkey;
+use anchor_lang::Result;
+use anchor_lang::{context::CpiContext, Accounts};
+use solana_program::account_info::AccountInfo;
+use spl_token_2022::state::AccountState;
+
+pub fn default_account_state_initialize<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, DefaultAccountStateInitialize<'info>>,
+    state: &AccountState,
+) -> Result<()> {
+    let ix = spl_token_2022::extension::default_account_state::instruction::initialize_default_account_state(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.mint.key,
+        state
+    )?;
+    solana_program::program::invoke_signed(
+        &ix,
+        &[ctx.accounts.token_program_id, ctx.accounts.mint],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct DefaultAccountStateInitialize<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub mint: AccountInfo<'info>,
+}
+
+pub fn default_account_state_update<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, DefaultAccountStateUpdate<'info>>,
+    state: &AccountState,
+) -> Result<()> {
+    let ix = spl_token_2022::extension::default_account_state::instruction::update_default_account_state(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.mint.key,
+        ctx.accounts.freeze_authority.key,
+        &[],
+        state
+    )?;
+
+    solana_program::program::invoke_signed(
+        &ix,
+        &[
+            ctx.accounts.token_program_id,
+            ctx.accounts.mint,
+            ctx.accounts.freeze_authority,
+        ],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct DefaultAccountStateUpdate<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub mint: AccountInfo<'info>,
+    pub freeze_authority: AccountInfo<'info>,
+}

+ 59 - 0
spl/src/token_2022_extensions/group_member_pointer.rs

@@ -0,0 +1,59 @@
+use anchor_lang::solana_program::account_info::AccountInfo;
+use anchor_lang::solana_program::pubkey::Pubkey;
+use anchor_lang::Result;
+use anchor_lang::{context::CpiContext, Accounts};
+
+pub fn group_member_pointer_initialize<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, GroupMemberPointerInitialize<'info>>,
+    authority: Option<Pubkey>,
+    member_address: Option<Pubkey>,
+) -> Result<()> {
+    let ix = spl_token_2022::extension::group_member_pointer::instruction::initialize(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.mint.key,
+        authority,
+        member_address,
+    )?;
+    solana_program::program::invoke_signed(
+        &ix,
+        &[ctx.accounts.token_program_id, ctx.accounts.mint],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct GroupMemberPointerInitialize<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub mint: AccountInfo<'info>,
+}
+
+pub fn group_member_pointer_update<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, GroupMemberPointerUpdate<'info>>,
+    member_address: Option<Pubkey>,
+) -> Result<()> {
+    let ix = spl_token_2022::extension::group_member_pointer::instruction::update(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.mint.key,
+        ctx.accounts.authority.key,
+        &[],
+        member_address,
+    )?;
+    solana_program::program::invoke_signed(
+        &ix,
+        &[
+            ctx.accounts.token_program_id,
+            ctx.accounts.mint,
+            ctx.accounts.authority,
+        ],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct GroupMemberPointerUpdate<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub mint: AccountInfo<'info>,
+    pub authority: AccountInfo<'info>,
+}

+ 55 - 0
spl/src/token_2022_extensions/group_pointer.rs

@@ -0,0 +1,55 @@
+use anchor_lang::solana_program::account_info::AccountInfo;
+use anchor_lang::solana_program::pubkey::Pubkey;
+use anchor_lang::Result;
+use anchor_lang::{context::CpiContext, Accounts};
+
+pub fn group_pointer_initialize<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, GroupPointerInitialize<'info>>,
+    authority: Option<Pubkey>,
+    group_address: Option<Pubkey>,
+) -> Result<()> {
+    let ix = spl_token_2022::extension::group_pointer::instruction::initialize(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.mint.key,
+        authority,
+        group_address,
+    )?;
+    solana_program::program::invoke_signed(
+        &ix,
+        &[ctx.accounts.token_program_id, ctx.accounts.mint],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct GroupPointerInitialize<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub mint: AccountInfo<'info>,
+}
+
+pub fn group_pointer_update<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, GroupPointerUpdate<'info>>,
+    group_address: Option<Pubkey>,
+) -> Result<()> {
+    let ix = spl_token_2022::extension::group_pointer::instruction::update(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.mint.key,
+        ctx.accounts.authority.key,
+        &[&ctx.accounts.authority.key],
+        group_address,
+    )?;
+    solana_program::program::invoke_signed(
+        &ix,
+        &[ctx.accounts.token_program_id, ctx.accounts.mint],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct GroupPointerUpdate<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub mint: AccountInfo<'info>,
+    pub authority: AccountInfo<'info>,
+}

+ 25 - 0
spl/src/token_2022_extensions/immutable_owner.rs

@@ -0,0 +1,25 @@
+use anchor_lang::solana_program::account_info::AccountInfo;
+use anchor_lang::solana_program::pubkey::Pubkey;
+use anchor_lang::Result;
+use anchor_lang::{context::CpiContext, Accounts};
+
+pub fn immutable_owner_initialize<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, ImmutableOwnerInitialize<'info>>,
+) -> Result<()> {
+    let ix = spl_token_2022::instruction::initialize_immutable_owner(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.token_account.key,
+    )?;
+    solana_program::program::invoke_signed(
+        &ix,
+        &[ctx.accounts.token_program_id, ctx.accounts.token_account],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct ImmutableOwnerInitialize<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub token_account: AccountInfo<'info>,
+}

+ 59 - 0
spl/src/token_2022_extensions/interest_bearing_mint.rs

@@ -0,0 +1,59 @@
+use anchor_lang::solana_program::account_info::AccountInfo;
+use anchor_lang::solana_program::pubkey::Pubkey;
+use anchor_lang::Result;
+use anchor_lang::{context::CpiContext, Accounts};
+
+pub fn interest_bearing_mint_initialize<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, InterestBearingMintInitialize<'info>>,
+    rate_authority: Option<Pubkey>,
+    rate: i16,
+) -> Result<()> {
+    let ix = spl_token_2022::extension::interest_bearing_mint::instruction::initialize(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.mint.key,
+        rate_authority,
+        rate,
+    )?;
+    solana_program::program::invoke_signed(
+        &ix,
+        &[ctx.accounts.token_program_id, ctx.accounts.mint],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct InterestBearingMintInitialize<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub mint: AccountInfo<'info>,
+}
+
+pub fn interest_bearing_mint_update_rate<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, InterestBearingMintUpdateRate<'info>>,
+    rate: i16,
+) -> Result<()> {
+    let ix = spl_token_2022::extension::interest_bearing_mint::instruction::update_rate(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.mint.key,
+        ctx.accounts.rate_authority.key,
+        &[],
+        rate,
+    )?;
+    solana_program::program::invoke_signed(
+        &ix,
+        &[
+            ctx.accounts.token_program_id,
+            ctx.accounts.mint,
+            ctx.accounts.rate_authority,
+        ],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct InterestBearingMintUpdateRate<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub mint: AccountInfo<'info>,
+    pub rate_authority: AccountInfo<'info>,
+}

+ 54 - 0
spl/src/token_2022_extensions/memo_transfer.rs

@@ -0,0 +1,54 @@
+use anchor_lang::solana_program::account_info::AccountInfo;
+use anchor_lang::solana_program::pubkey::Pubkey;
+use anchor_lang::Result;
+use anchor_lang::{context::CpiContext, Accounts};
+
+pub fn memo_transfer_initialize<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, MemoTransfer<'info>>,
+) -> Result<()> {
+    let ix = spl_token_2022::extension::memo_transfer::instruction::enable_required_transfer_memos(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.account.key,
+        ctx.accounts.owner.key,
+        &[],
+    )?;
+    solana_program::program::invoke_signed(
+        &ix,
+        &[
+            ctx.accounts.token_program_id,
+            ctx.accounts.account,
+            ctx.accounts.owner,
+        ],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+pub fn memo_transfer_disable<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, MemoTransfer<'info>>,
+) -> Result<()> {
+    let ix =
+        spl_token_2022::extension::memo_transfer::instruction::disable_required_transfer_memos(
+            ctx.accounts.token_program_id.key,
+            ctx.accounts.account.key,
+            ctx.accounts.owner.key,
+            &[],
+        )?;
+    solana_program::program::invoke_signed(
+        &ix,
+        &[
+            ctx.accounts.token_program_id,
+            ctx.accounts.account,
+            ctx.accounts.owner,
+        ],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct MemoTransfer<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub account: AccountInfo<'info>,
+    pub owner: AccountInfo<'info>,
+}

+ 29 - 0
spl/src/token_2022_extensions/metadata_pointer.rs

@@ -0,0 +1,29 @@
+use anchor_lang::solana_program::account_info::AccountInfo;
+use anchor_lang::solana_program::pubkey::Pubkey;
+use anchor_lang::Result;
+use anchor_lang::{context::CpiContext, Accounts};
+
+pub fn metadata_pointer_initialize<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, MetadataPointerInitialize<'info>>,
+    authority: Option<Pubkey>,
+    metadata_address: Option<Pubkey>,
+) -> Result<()> {
+    let ix = spl_token_2022::extension::metadata_pointer::instruction::initialize(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.mint.key,
+        authority,
+        metadata_address,
+    )?;
+    solana_program::program::invoke_signed(
+        &ix,
+        &[ctx.accounts.token_program_id, ctx.accounts.mint],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct MetadataPointerInitialize<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub mint: AccountInfo<'info>,
+}

+ 27 - 0
spl/src/token_2022_extensions/mint_close_authority.rs

@@ -0,0 +1,27 @@
+use anchor_lang::solana_program::account_info::AccountInfo;
+use anchor_lang::solana_program::pubkey::Pubkey;
+use anchor_lang::Result;
+use anchor_lang::{context::CpiContext, Accounts};
+
+pub fn mint_close_authority_initialize<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, MintCloseAuthorityInitialize<'info>>,
+    authority: Option<&Pubkey>,
+) -> Result<()> {
+    let ix = spl_token_2022::instruction::initialize_mint_close_authority(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.mint.key,
+        authority,
+    )?;
+    solana_program::program::invoke_signed(
+        &ix,
+        &[ctx.accounts.token_program_id, ctx.accounts.mint],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct MintCloseAuthorityInitialize<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub mint: AccountInfo<'info>,
+}

+ 36 - 0
spl/src/token_2022_extensions/mod.rs

@@ -0,0 +1,36 @@
+pub mod confidential_transfer;
+pub mod confidential_transfer_fee;
+pub mod cpi_guard;
+pub mod default_account_state;
+pub mod group_member_pointer;
+pub mod group_pointer;
+pub mod immutable_owner;
+pub mod interest_bearing_mint;
+pub mod memo_transfer;
+pub mod metadata_pointer;
+pub mod mint_close_authority;
+pub mod non_transferable;
+pub mod permanent_delegate;
+pub mod token_group;
+pub mod token_metadata;
+pub mod transfer_fee;
+pub mod transfer_hook;
+
+pub use cpi_guard::*;
+pub use default_account_state::*;
+pub use group_member_pointer::*;
+pub use group_pointer::*;
+pub use immutable_owner::*;
+pub use interest_bearing_mint::*;
+pub use memo_transfer::*;
+pub use metadata_pointer::*;
+pub use mint_close_authority::*;
+pub use non_transferable::*;
+pub use permanent_delegate::*;
+pub use token_group::*;
+pub use token_metadata::*;
+pub use transfer_fee::*;
+pub use transfer_hook::*;
+
+pub use spl_pod;
+pub use spl_token_metadata_interface;

+ 25 - 0
spl/src/token_2022_extensions/non_transferable.rs

@@ -0,0 +1,25 @@
+use anchor_lang::solana_program::account_info::AccountInfo;
+use anchor_lang::solana_program::pubkey::Pubkey;
+use anchor_lang::Result;
+use anchor_lang::{context::CpiContext, Accounts};
+
+pub fn non_transferable_mint_initialize<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, NonTransferableMintInitialize<'info>>,
+) -> Result<()> {
+    let ix = spl_token_2022::instruction::initialize_non_transferable_mint(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.mint.key,
+    )?;
+    solana_program::program::invoke_signed(
+        &ix,
+        &[ctx.accounts.token_program_id, ctx.accounts.mint],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct NonTransferableMintInitialize<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub mint: AccountInfo<'info>,
+}

+ 27 - 0
spl/src/token_2022_extensions/permanent_delegate.rs

@@ -0,0 +1,27 @@
+use anchor_lang::solana_program::account_info::AccountInfo;
+use anchor_lang::solana_program::pubkey::Pubkey;
+use anchor_lang::Result;
+use anchor_lang::{context::CpiContext, Accounts};
+
+pub fn permanent_delegate_initialize<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, PermanentDelegateInitialize<'info>>,
+    permanent_delegate: &Pubkey,
+) -> Result<()> {
+    let ix = spl_token_2022::instruction::initialize_permanent_delegate(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.mint.key,
+        permanent_delegate,
+    )?;
+    solana_program::program::invoke_signed(
+        &ix,
+        &[ctx.accounts.token_program_id, ctx.accounts.mint],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct PermanentDelegateInitialize<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub mint: AccountInfo<'info>,
+}

+ 74 - 0
spl/src/token_2022_extensions/token_group.rs

@@ -0,0 +1,74 @@
+use anchor_lang::solana_program::account_info::AccountInfo;
+use anchor_lang::solana_program::pubkey::Pubkey;
+use anchor_lang::Result;
+use anchor_lang::{context::CpiContext, Accounts};
+
+pub fn token_group_initialize<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, TokenGroupInitialize<'info>>,
+    update_authority: Option<Pubkey>,
+    max_size: u32,
+) -> Result<()> {
+    let ix = spl_token_group_interface::instruction::initialize_group(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.group.key,
+        ctx.accounts.mint.key,
+        ctx.accounts.mint_authority.key,
+        update_authority,
+        max_size,
+    );
+    solana_program::program::invoke_signed(
+        &ix,
+        &[
+            ctx.accounts.token_program_id,
+            ctx.accounts.group,
+            ctx.accounts.mint,
+            ctx.accounts.mint_authority,
+        ],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct TokenGroupInitialize<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub group: AccountInfo<'info>,
+    pub mint: AccountInfo<'info>,
+    pub mint_authority: AccountInfo<'info>,
+}
+
+pub fn token_member_initialize<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, TokenMemberInitialize<'info>>,
+) -> Result<()> {
+    let ix = spl_token_group_interface::instruction::initialize_member(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.member.key,
+        ctx.accounts.member_mint.key,
+        ctx.accounts.member_mint_authority.key,
+        ctx.accounts.group.key,
+        ctx.accounts.group_update_authority.key,
+    );
+    solana_program::program::invoke_signed(
+        &ix,
+        &[
+            ctx.accounts.token_program_id,
+            ctx.accounts.member,
+            ctx.accounts.member_mint,
+            ctx.accounts.member_mint_authority,
+            ctx.accounts.group,
+            ctx.accounts.group_update_authority,
+        ],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct TokenMemberInitialize<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub member: AccountInfo<'info>,
+    pub member_mint: AccountInfo<'info>,
+    pub member_mint_authority: AccountInfo<'info>,
+    pub group: AccountInfo<'info>,
+    pub group_update_authority: AccountInfo<'info>,
+}

+ 107 - 0
spl/src/token_2022_extensions/token_metadata.rs

@@ -0,0 +1,107 @@
+use anchor_lang::solana_program::account_info::AccountInfo;
+use anchor_lang::solana_program::pubkey::Pubkey;
+use anchor_lang::Result;
+use anchor_lang::{context::CpiContext, Accounts};
+
+use spl_pod::optional_keys::OptionalNonZeroPubkey;
+use spl_token_metadata_interface::state::Field;
+
+pub fn token_metadata_initialize<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, TokenMetadataInitialize<'info>>,
+    name: String,
+    symbol: String,
+    uri: String,
+) -> Result<()> {
+    let ix = spl_token_metadata_interface::instruction::initialize(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.metadata.key,
+        ctx.accounts.update_authority.key,
+        ctx.accounts.mint.key,
+        ctx.accounts.mint_authority.key,
+        name,
+        symbol,
+        uri,
+    );
+    solana_program::program::invoke_signed(
+        &ix,
+        &[
+            ctx.accounts.token_program_id,
+            ctx.accounts.metadata,
+            ctx.accounts.update_authority,
+            ctx.accounts.mint,
+            ctx.accounts.mint_authority,
+        ],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct TokenMetadataInitialize<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub metadata: AccountInfo<'info>,
+    pub update_authority: AccountInfo<'info>,
+    pub mint_authority: AccountInfo<'info>,
+    pub mint: AccountInfo<'info>,
+}
+
+pub fn token_metadata_update_authority<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, TokenMetadataUpdateAuthority<'info>>,
+    new_authority: OptionalNonZeroPubkey,
+) -> Result<()> {
+    let ix = spl_token_metadata_interface::instruction::update_authority(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.metadata.key,
+        ctx.accounts.current_authority.key,
+        new_authority,
+    );
+    solana_program::program::invoke_signed(
+        &ix,
+        &[
+            ctx.accounts.token_program_id,
+            ctx.accounts.metadata,
+            ctx.accounts.current_authority,
+        ],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct TokenMetadataUpdateAuthority<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub metadata: AccountInfo<'info>,
+    pub current_authority: AccountInfo<'info>,
+    pub new_authority: AccountInfo<'info>,
+}
+
+pub fn token_metadata_update_field<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, TokenMetadataUpdateField<'info>>,
+    field: Field,
+    value: String,
+) -> Result<()> {
+    let ix = spl_token_metadata_interface::instruction::update_field(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.metadata.key,
+        ctx.accounts.update_authority.key,
+        field,
+        value,
+    );
+    solana_program::program::invoke_signed(
+        &ix,
+        &[
+            ctx.accounts.token_program_id,
+            ctx.accounts.metadata,
+            ctx.accounts.update_authority,
+        ],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct TokenMetadataUpdateField<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub metadata: AccountInfo<'info>,
+    pub update_authority: AccountInfo<'info>,
+}

+ 160 - 0
spl/src/token_2022_extensions/transfer_fee.rs

@@ -0,0 +1,160 @@
+use anchor_lang::solana_program::account_info::AccountInfo;
+use anchor_lang::solana_program::pubkey::Pubkey;
+use anchor_lang::Result;
+use anchor_lang::{context::CpiContext, Accounts};
+
+pub fn transfer_fee_initialize<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, TransferFeeInitialize<'info>>,
+    transfer_fee_config_authority: Option<&Pubkey>,
+    withdraw_withheld_authority: Option<&Pubkey>,
+    transfer_fee_basis_points: u16,
+    maximum_fee: u64,
+) -> Result<()> {
+    let ix = spl_token_2022::extension::transfer_fee::instruction::initialize_transfer_fee_config(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.mint.key,
+        transfer_fee_config_authority,
+        withdraw_withheld_authority,
+        transfer_fee_basis_points,
+        maximum_fee,
+    )?;
+    solana_program::program::invoke_signed(
+        &ix,
+        &[ctx.accounts.token_program_id, ctx.accounts.mint],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct TransferFeeInitialize<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub mint: AccountInfo<'info>,
+}
+
+pub fn transfer_fee_set<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, TransferFeeSetTransferFee<'info>>,
+    transfer_fee_basis_points: u16,
+    maximum_fee: u64,
+) -> Result<()> {
+    let ix = spl_token_2022::extension::transfer_fee::instruction::set_transfer_fee(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.mint.key,
+        ctx.accounts.authority.key,
+        &[],
+        transfer_fee_basis_points,
+        maximum_fee,
+    )?;
+    solana_program::program::invoke_signed(
+        &ix,
+        &[
+            ctx.accounts.token_program_id,
+            ctx.accounts.mint,
+            ctx.accounts.authority,
+        ],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct TransferFeeSetTransferFee<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub mint: AccountInfo<'info>,
+    pub authority: AccountInfo<'info>,
+}
+
+pub fn transfer_checked_with_fee<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, TransferCheckedWithFee<'info>>,
+    amount: u64,
+    decimals: u8,
+    fee: u64,
+) -> Result<()> {
+    let ix = spl_token_2022::extension::transfer_fee::instruction::transfer_checked_with_fee(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.source.key,
+        ctx.accounts.mint.key,
+        ctx.accounts.destination.key,
+        ctx.accounts.authority.key,
+        &[],
+        amount,
+        decimals,
+        fee,
+    )?;
+    solana_program::program::invoke_signed(
+        &ix,
+        &[
+            ctx.accounts.token_program_id,
+            ctx.accounts.source,
+            ctx.accounts.mint,
+            ctx.accounts.destination,
+            ctx.accounts.authority,
+        ],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct TransferCheckedWithFee<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub source: AccountInfo<'info>,
+    pub mint: AccountInfo<'info>,
+    pub destination: AccountInfo<'info>,
+    pub authority: AccountInfo<'info>,
+}
+
+pub fn harvest_withheld_tokens_to_mint<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, HarvestWithheldTokensToMint<'info>>,
+    sources: Vec<AccountInfo<'info>>,
+) -> Result<()> {
+    let ix = spl_token_2022::extension::transfer_fee::instruction::harvest_withheld_tokens_to_mint(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.mint.key,
+        sources.iter().map(|a| a.key).collect::<Vec<_>>().as_slice(),
+    )?;
+
+    let mut account_infos = vec![ctx.accounts.token_program_id, ctx.accounts.mint];
+    account_infos.extend_from_slice(&sources);
+
+    solana_program::program::invoke_signed(&ix, &account_infos, ctx.signer_seeds)
+        .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct HarvestWithheldTokensToMint<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub mint: AccountInfo<'info>,
+}
+
+pub fn withdraw_withheld_tokens_from_mint<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, WithdrawWithheldTokensFromMint<'info>>,
+) -> Result<()> {
+    let ix =
+        spl_token_2022::extension::transfer_fee::instruction::withdraw_withheld_tokens_from_mint(
+            ctx.accounts.token_program_id.key,
+            ctx.accounts.mint.key,
+            ctx.accounts.destination.key,
+            ctx.accounts.authority.key,
+            &[],
+        )?;
+    solana_program::program::invoke_signed(
+        &ix,
+        &[
+            ctx.accounts.token_program_id,
+            ctx.accounts.mint,
+            ctx.accounts.destination,
+            ctx.accounts.authority,
+        ],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct WithdrawWithheldTokensFromMint<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub mint: AccountInfo<'info>,
+    pub destination: AccountInfo<'info>,
+    pub authority: AccountInfo<'info>,
+}

+ 59 - 0
spl/src/token_2022_extensions/transfer_hook.rs

@@ -0,0 +1,59 @@
+use anchor_lang::solana_program::account_info::AccountInfo;
+use anchor_lang::solana_program::pubkey::Pubkey;
+use anchor_lang::Result;
+use anchor_lang::{context::CpiContext, Accounts};
+
+pub fn transfer_hook_initialize<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, TransferHookInitialize<'info>>,
+    authority: Option<Pubkey>,
+    transfer_hook_program_id: Option<Pubkey>,
+) -> Result<()> {
+    let ix = spl_token_2022::extension::transfer_hook::instruction::initialize(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.mint.key,
+        authority,
+        transfer_hook_program_id,
+    )?;
+    solana_program::program::invoke_signed(
+        &ix,
+        &[ctx.accounts.token_program_id, ctx.accounts.mint],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct TransferHookInitialize<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub mint: AccountInfo<'info>,
+}
+
+pub fn transfer_hook_update<'info>(
+    ctx: CpiContext<'_, '_, '_, 'info, TransferHookUpdate<'info>>,
+    transfer_hook_program_id: Option<Pubkey>,
+) -> Result<()> {
+    let ix = spl_token_2022::extension::transfer_hook::instruction::update(
+        ctx.accounts.token_program_id.key,
+        ctx.accounts.mint.key,
+        ctx.accounts.authority.key,
+        &[],
+        transfer_hook_program_id,
+    )?;
+    solana_program::program::invoke_signed(
+        &ix,
+        &[
+            ctx.accounts.token_program_id,
+            ctx.accounts.mint,
+            ctx.accounts.authority,
+        ],
+        ctx.signer_seeds,
+    )
+    .map_err(Into::into)
+}
+
+#[derive(Accounts)]
+pub struct TransferHookUpdate<'info> {
+    pub token_program_id: AccountInfo<'info>,
+    pub mint: AccountInfo<'info>,
+    pub authority: AccountInfo<'info>,
+}

+ 30 - 0
spl/src/token_interface.rs

@@ -1,7 +1,15 @@
 use anchor_lang::solana_program::pubkey::Pubkey;
+use solana_program::program_pack::Pack;
+use spl_token_2022::extension::ExtensionType;
+use spl_token_2022::{
+    extension::{BaseStateWithExtensions, Extension, StateWithExtensions},
+    solana_zk_token_sdk::instruction::Pod,
+};
 use std::ops::Deref;
 
 pub use crate::token_2022::*;
+#[cfg(feature = "token_2022_extensions")]
+pub use crate::token_2022_extensions::*;
 
 static IDS: [Pubkey; 2] = [spl_token::ID, spl_token_2022::ID];
 
@@ -69,3 +77,25 @@ impl anchor_lang::Ids for TokenInterface {
         &IDS
     }
 }
+
+pub type ExtensionsVec = Vec<ExtensionType>;
+
+pub fn find_mint_account_size(extensions: Option<&ExtensionsVec>) -> anchor_lang::Result<usize> {
+    if let Some(extensions) = extensions {
+        Ok(ExtensionType::try_calculate_account_len::<
+            spl_token_2022::state::Mint,
+        >(extensions)?)
+    } else {
+        Ok(spl_token_2022::state::Mint::LEN)
+    }
+}
+
+pub fn get_mint_extension_data<T: Extension + Pod>(
+    account: &solana_program::account_info::AccountInfo,
+) -> anchor_lang::Result<T> {
+    let mint_data = account.data.borrow();
+    let mint_with_extension =
+        StateWithExtensions::<spl_token_2022::state::Mint>::unpack(&mint_data)?;
+    let extension_data = *mint_with_extension.get_extension::<T>()?;
+    Ok(extension_data)
+}

+ 1 - 0
tests/package.json

@@ -34,6 +34,7 @@
     "pyth",
     "realloc",
     "spl/metadata",
+    "spl/token-extensions",
     "spl/token-proxy",
     "spl/token-wrapper",
     "spl/transfer-hook",

+ 14 - 0
tests/spl/token-extensions/Anchor.toml

@@ -0,0 +1,14 @@
+[provider]
+cluster = "localnet"
+wallet = "~/.config/solana/id.json"
+
+[programs.localnet]
+token_extensions = "tKEkkQtgMXhdaz5NMTR3XbdUu215sZyHSj6Menvous1"
+
+[scripts]
+test = "yarn run ts-mocha -t 1000000 tests/*.ts"
+
+[features]
+
+[test.validator]
+url = "https://api.mainnet-beta.solana.com"

+ 8 - 0
tests/spl/token-extensions/Cargo.toml

@@ -0,0 +1,8 @@
+[workspace]
+members = [
+    "programs/*"
+]
+resolver = "2"
+
+[profile.release]
+overflow-checks = true

+ 22 - 0
tests/spl/token-extensions/package.json

@@ -0,0 +1,22 @@
+{
+  "name": "token-extensions",
+  "version": "0.29.0",
+  "license": "(MIT OR Apache-2.0)",
+  "homepage": "https://github.com/coral-xyz/anchor#readme",
+  "bugs": {
+    "url": "https://github.com/coral-xyz/anchor/issues"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/coral-xyz/anchor.git"
+  },
+  "engines": {
+    "node": ">=11"
+  },
+  "scripts": {
+    "test": "anchor test"
+  },
+  "dependencies": {
+    "@solana/spl-token": "^0.3.9"
+  }
+}

+ 25 - 0
tests/spl/token-extensions/programs/token-extensions/Cargo.toml

@@ -0,0 +1,25 @@
+[package]
+name = "token-extensions"
+version = "0.1.0"
+description = "Created with Anchor"
+rust-version = "1.60"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "token_extensions"
+
+[features]
+no-entrypoint = []
+no-idl = []
+cpi = ["no-entrypoint"]
+default = []
+idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
+
+[dependencies]
+anchor-lang = { path = "../../../../../lang", features = ["init-if-needed"] }
+anchor-spl = { path = "../../../../../spl" }
+spl-tlv-account-resolution = "0.6.3"
+spl-transfer-hook-interface = "0.6.3"
+spl-type-length-value = "0.4.3"
+spl-pod = "0.2.2"

+ 2 - 0
tests/spl/token-extensions/programs/token-extensions/Xargo.toml

@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []

+ 180 - 0
tests/spl/token-extensions/programs/token-extensions/src/instructions.rs

@@ -0,0 +1,180 @@
+use anchor_lang::{prelude::*, solana_program::entrypoint::ProgramResult};
+
+use anchor_spl::{
+    associated_token::AssociatedToken,
+    token_2022::spl_token_2022::extension::{
+        group_member_pointer::GroupMemberPointer, metadata_pointer::MetadataPointer,
+        mint_close_authority::MintCloseAuthority, permanent_delegate::PermanentDelegate,
+        transfer_hook::TransferHook,
+    },
+    token_interface::{
+        spl_token_metadata_interface::state::TokenMetadata, token_metadata_initialize, Mint,
+        Token2022, TokenAccount, TokenMetadataInitialize,
+    },
+};
+use spl_pod::optional_keys::OptionalNonZeroPubkey;
+
+use crate::{
+    get_meta_list_size, get_mint_extensible_extension_data, get_mint_extension_data,
+    update_account_lamports_to_minimum_balance, META_LIST_ACCOUNT_SEED,
+};
+
+#[derive(AnchorDeserialize, AnchorSerialize)]
+pub struct CreateMintAccountArgs {
+    pub name: String,
+    pub symbol: String,
+    pub uri: String,
+}
+
+#[derive(Accounts)]
+#[instruction(args: CreateMintAccountArgs)]
+pub struct CreateMintAccount<'info> {
+    #[account(mut)]
+    pub payer: Signer<'info>,
+    #[account(mut)]
+    /// CHECK: can be any account
+    pub authority: Signer<'info>,
+    #[account()]
+    /// CHECK: can be any account
+    pub receiver: UncheckedAccount<'info>,
+    #[account(
+        init,
+        signer,
+        payer = payer,
+        mint::token_program = token_program,
+        mint::decimals = 0,
+        mint::authority = authority,
+        mint::freeze_authority = authority,
+        extensions::metadata_pointer::authority = authority,
+        extensions::metadata_pointer::metadata_address = mint,
+        extensions::group_member_pointer::authority = authority,
+        extensions::group_member_pointer::member_address = mint,
+        extensions::transfer_hook::authority = authority,
+        extensions::transfer_hook::program_id = crate::ID,
+        extensions::close_authority::authority = authority,
+        extensions::permanent_delegate::delegate = authority,
+    )]
+    pub mint: Box<InterfaceAccount<'info, Mint>>,
+    #[account(
+        init,
+        payer = payer,
+        associated_token::token_program = token_program,
+        associated_token::mint = mint,
+        associated_token::authority = receiver,
+    )]
+    pub mint_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
+    /// CHECK: This account's data is a buffer of TLV data
+    #[account(
+        init,
+        space = get_meta_list_size(None),
+        seeds = [META_LIST_ACCOUNT_SEED, mint.key().as_ref()],
+        bump,
+        payer = payer,
+    )]
+    pub extra_metas_account: UncheckedAccount<'info>,
+    pub system_program: Program<'info, System>,
+    pub associated_token_program: Program<'info, AssociatedToken>,
+    pub token_program: Program<'info, Token2022>,
+}
+
+impl<'info> CreateMintAccount<'info> {
+    fn initialize_token_metadata(
+        &self,
+        name: String,
+        symbol: String,
+        uri: String,
+    ) -> ProgramResult {
+        let cpi_accounts = TokenMetadataInitialize {
+            token_program_id: self.token_program.to_account_info(),
+            mint: self.mint.to_account_info(),
+            metadata: self.mint.to_account_info(), // metadata account is the mint, since data is stored in mint
+            mint_authority: self.authority.to_account_info(),
+            update_authority: self.authority.to_account_info(),
+        };
+        let cpi_ctx = CpiContext::new(self.token_program.to_account_info(), cpi_accounts);
+        token_metadata_initialize(cpi_ctx, name, symbol, uri)?;
+        Ok(())
+    }
+}
+
+pub fn handler(ctx: Context<CreateMintAccount>, args: CreateMintAccountArgs) -> Result<()> {
+    ctx.accounts.initialize_token_metadata(
+        args.name.clone(),
+        args.symbol.clone(),
+        args.uri.clone(),
+    )?;
+    ctx.accounts.mint.reload()?;
+    let mint_data = &mut ctx.accounts.mint.to_account_info();
+    let metadata = get_mint_extensible_extension_data::<TokenMetadata>(mint_data)?;
+    assert_eq!(metadata.mint, ctx.accounts.mint.key());
+    assert_eq!(metadata.name, args.name);
+    assert_eq!(metadata.symbol, args.symbol);
+    assert_eq!(metadata.uri, args.uri);
+    let metadata_pointer = get_mint_extension_data::<MetadataPointer>(mint_data)?;
+    let mint_key: Option<Pubkey> = Some(ctx.accounts.mint.key());
+    let authority_key: Option<Pubkey> = Some(ctx.accounts.authority.key());
+    assert_eq!(
+        metadata_pointer.metadata_address,
+        OptionalNonZeroPubkey::try_from(mint_key)?
+    );
+    assert_eq!(
+        metadata_pointer.authority,
+        OptionalNonZeroPubkey::try_from(authority_key)?
+    );
+    let permanent_delegate = get_mint_extension_data::<PermanentDelegate>(mint_data)?;
+    assert_eq!(
+        permanent_delegate.delegate,
+        OptionalNonZeroPubkey::try_from(authority_key)?
+    );
+    let close_authority = get_mint_extension_data::<MintCloseAuthority>(mint_data)?;
+    assert_eq!(
+        close_authority.close_authority,
+        OptionalNonZeroPubkey::try_from(authority_key)?
+    );
+    let transfer_hook = get_mint_extension_data::<TransferHook>(mint_data)?;
+    let program_id: Option<Pubkey> = Some(ctx.program_id.key());
+    assert_eq!(
+        transfer_hook.authority,
+        OptionalNonZeroPubkey::try_from(authority_key)?
+    );
+    assert_eq!(
+        transfer_hook.program_id,
+        OptionalNonZeroPubkey::try_from(program_id)?
+    );
+    let group_member_pointer = get_mint_extension_data::<GroupMemberPointer>(mint_data)?;
+    assert_eq!(
+        group_member_pointer.authority,
+        OptionalNonZeroPubkey::try_from(authority_key)?
+    );
+    assert_eq!(
+        group_member_pointer.member_address,
+        OptionalNonZeroPubkey::try_from(mint_key)?
+    );
+    // transfer minimum rent to mint account
+    update_account_lamports_to_minimum_balance(
+        ctx.accounts.mint.to_account_info(),
+        ctx.accounts.payer.to_account_info(),
+        ctx.accounts.system_program.to_account_info(),
+    )?;
+
+    Ok(())
+}
+
+#[derive(Accounts)]
+#[instruction()]
+pub struct CheckMintExtensionConstraints<'info> {
+    #[account(mut)]
+    /// CHECK: can be any account
+    pub authority: Signer<'info>,
+    #[account(
+        extensions::metadata_pointer::authority = authority,
+        extensions::metadata_pointer::metadata_address = mint,
+        extensions::group_member_pointer::authority = authority,
+        extensions::group_member_pointer::member_address = mint,
+        extensions::transfer_hook::authority = authority,
+        extensions::transfer_hook::program_id = crate::ID,
+        extensions::close_authority::authority = authority,
+        extensions::permanent_delegate::delegate = authority,
+    )]
+    pub mint: Box<InterfaceAccount<'info, Mint>>,
+}

+ 32 - 0
tests/spl/token-extensions/programs/token-extensions/src/lib.rs

@@ -0,0 +1,32 @@
+//! An example of a program with token extensions enabled
+//!
+//! This program is intended to implement various token2022 extensions
+//!
+//! <https://spl.solana.com/token-2022/extensions>
+
+use anchor_lang::prelude::*;
+
+pub mod instructions;
+pub mod utils;
+pub use instructions::*;
+pub use utils::*;
+
+declare_id!("tKEkkQtgMXhdaz5NMTR3XbdUu215sZyHSj6Menvous1");
+
+#[program]
+pub mod token_extensions {
+    use super::*;
+
+    pub fn create_mint_account(
+        ctx: Context<CreateMintAccount>,
+        args: CreateMintAccountArgs,
+    ) -> Result<()> {
+        instructions::handler(ctx, args)
+    }
+
+    pub fn check_mint_extensions_constraints(
+        _ctx: Context<CheckMintExtensionConstraints>,
+    ) -> Result<()> {
+        Ok(())
+    }
+}

+ 88 - 0
tests/spl/token-extensions/programs/token-extensions/src/utils.rs

@@ -0,0 +1,88 @@
+use anchor_lang::{
+    prelude::Result,
+    solana_program::{
+        account_info::AccountInfo,
+        instruction::{get_stack_height, TRANSACTION_LEVEL_STACK_HEIGHT},
+        program::invoke,
+        pubkey::Pubkey,
+        rent::Rent,
+        system_instruction::transfer,
+        sysvar::Sysvar,
+    },
+    Lamports,
+};
+use anchor_spl::token_interface::spl_token_2022::{
+    extension::{BaseStateWithExtensions, Extension, StateWithExtensions},
+    solana_zk_token_sdk::zk_token_proof_instruction::Pod,
+    state::Mint,
+};
+use spl_tlv_account_resolution::{account::ExtraAccountMeta, state::ExtraAccountMetaList};
+use spl_type_length_value::variable_len_pack::VariableLenPack;
+
+pub const APPROVE_ACCOUNT_SEED: &[u8] = b"approve-account";
+pub const META_LIST_ACCOUNT_SEED: &[u8] = b"extra-account-metas";
+
+pub fn update_account_lamports_to_minimum_balance<'info>(
+    account: AccountInfo<'info>,
+    payer: AccountInfo<'info>,
+    system_program: AccountInfo<'info>,
+) -> Result<()> {
+    let extra_lamports = Rent::get()?.minimum_balance(account.data_len()) - account.get_lamports();
+    if extra_lamports > 0 {
+        invoke(
+            &transfer(payer.key, account.key, extra_lamports),
+            &[payer, account, system_program],
+        )?;
+    }
+    Ok(())
+}
+
+pub fn get_mint_extensible_extension_data<T: Extension + VariableLenPack>(
+    account: &mut AccountInfo,
+) -> Result<T> {
+    let mint_data = account.data.borrow();
+    let mint_with_extension = StateWithExtensions::<Mint>::unpack(&mint_data)?;
+    let extension_data = mint_with_extension.get_variable_len_extension::<T>()?;
+    Ok(extension_data)
+}
+
+pub fn get_mint_extension_data<T: Extension + Pod>(account: &mut AccountInfo) -> Result<T> {
+    let mint_data = account.data.borrow();
+    let mint_with_extension = StateWithExtensions::<Mint>::unpack(&mint_data)?;
+    let extension_data = *mint_with_extension.get_extension::<T>()?;
+    Ok(extension_data)
+}
+
+pub fn get_extra_meta_list_account_pda(mint: Pubkey) -> Pubkey {
+    Pubkey::find_program_address(&[META_LIST_ACCOUNT_SEED, mint.as_ref()], &crate::id()).0
+}
+
+pub fn get_approve_account_pda(mint: Pubkey) -> Pubkey {
+    Pubkey::find_program_address(&[APPROVE_ACCOUNT_SEED, mint.as_ref()], &crate::id()).0
+}
+
+/// Determine if we are in CPI
+pub fn hook_in_cpi() -> bool {
+    let stack_height = get_stack_height();
+    let tx_height = TRANSACTION_LEVEL_STACK_HEIGHT;
+    let hook_height: usize = tx_height + 1;
+
+    stack_height > hook_height
+}
+
+pub fn get_meta_list(approve_account: Option<Pubkey>) -> Vec<ExtraAccountMeta> {
+    if let Some(approve_account) = approve_account {
+        return vec![ExtraAccountMeta {
+            discriminator: 0,
+            address_config: approve_account.to_bytes(),
+            is_signer: false.into(),
+            is_writable: true.into(),
+        }];
+    }
+    vec![]
+}
+
+pub fn get_meta_list_size(approve_account: Option<Pubkey>) -> usize {
+    // safe because it's either 0 or 1
+    ExtraAccountMetaList::size_of(get_meta_list(approve_account).len()).unwrap()
+}

+ 84 - 0
tests/spl/token-extensions/tests/token-extensions.ts

@@ -0,0 +1,84 @@
+import * as anchor from "@coral-xyz/anchor";
+import { Program } from "@coral-xyz/anchor";
+import { PublicKey, Keypair } from "@solana/web3.js";
+import { TokenExtensions } from "../target/types/token_extensions";
+import { ASSOCIATED_PROGRAM_ID } from "@coral-xyz/anchor/dist/cjs/utils/token";
+import { it } from "node:test";
+
+const TOKEN_2022_PROGRAM_ID = new anchor.web3.PublicKey(
+  "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
+);
+
+export function associatedAddress({
+  mint,
+  owner,
+}: {
+  mint: PublicKey;
+  owner: PublicKey;
+}): PublicKey {
+  return PublicKey.findProgramAddressSync(
+    [owner.toBuffer(), TOKEN_2022_PROGRAM_ID.toBuffer(), mint.toBuffer()],
+    ASSOCIATED_PROGRAM_ID
+  )[0];
+}
+
+describe("token extensions", () => {
+  const provider = anchor.AnchorProvider.env();
+  anchor.setProvider(provider);
+
+  const program = anchor.workspace.TokenExtensions as Program<TokenExtensions>;
+
+  const payer = Keypair.generate();
+
+  it("airdrop payer", async () => {
+    await provider.connection.confirmTransaction(
+      await provider.connection.requestAirdrop(payer.publicKey, 10000000000),
+      "confirmed"
+    );
+  });
+
+  let mint = new Keypair();
+
+  it("Create mint account test passes", async () => {
+    const [extraMetasAccount] = PublicKey.findProgramAddressSync(
+      [
+        anchor.utils.bytes.utf8.encode("extra-account-metas"),
+        mint.publicKey.toBuffer(),
+      ],
+      program.programId
+    );
+    await program.methods
+      .createMintAccount({
+        name: "hello",
+        symbol: "hi",
+        uri: "https://hi.com",
+      })
+      .accountsStrict({
+        payer: payer.publicKey,
+        authority: payer.publicKey,
+        receiver: payer.publicKey,
+        mint: mint.publicKey,
+        mintTokenAccount: associatedAddress({
+          mint: mint.publicKey,
+          owner: payer.publicKey,
+        }),
+        extraMetasAccount: extraMetasAccount,
+        systemProgram: anchor.web3.SystemProgram.programId,
+        associatedTokenProgram: ASSOCIATED_PROGRAM_ID,
+        tokenProgram: TOKEN_2022_PROGRAM_ID,
+      })
+      .signers([mint, payer])
+      .rpc();
+  });
+
+  it("mint extension constraints test passes", async () => {
+    await program.methods
+      .checkMintExtensionsConstraints()
+      .accountsStrict({
+        authority: payer.publicKey,
+        mint: mint.publicKey,
+      })
+      .signers([payer])
+      .rpc();
+  });
+});

+ 11 - 0
tests/spl/token-extensions/tsconfig.json

@@ -0,0 +1,11 @@
+{
+  "compilerOptions": {
+    "types": ["mocha", "chai", "node"],
+    "typeRoots": ["./node_modules/@types"],
+    "lib": ["es2015"],
+    "module": "commonjs",
+    "target": "es6",
+    "esModuleInterop": true,
+    "skipLibCheck": true
+  }
+}

+ 2 - 1
tests/spl/token-proxy/programs/token-proxy/src/lib.rs

@@ -191,7 +191,8 @@ pub struct ProxyCreateAssociatedTokenAccount<'info> {
 pub struct ProxyCreateMint<'info> {
     #[account(mut)]
     pub authority: Signer<'info>,
-    #[account(init,
+    #[account(
+        init,
         mint::decimals = 9,
         mint::authority = authority,
         seeds = [authority.key().as_ref(), name.as_bytes(), b"token-proxy-mint"],