Add support for token extensions (#2789)

This commit is contained in:
Bhargava Sai Macha 2024-04-11 16:49:13 -04:00 committed by GitHub
parent ae26fd84bb
commit e3ced784ad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
42 changed files with 2464 additions and 24 deletions

View File

@ -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

View File

@ -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

19
Cargo.lock generated
View File

@ -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",
]

View File

@ -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"] }

View File

@ -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

View File

@ -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 create_account = generate_create_account(
field,
quote! {::anchor_spl::token::Mint::LEN},
quote! {&#token_program.key()},
quote! {#payer},
seeds_with_bump,
);
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,
mint_space,
quote! {&#token_program.key()},
quote! {#payer},
seeds_with_bump,
);
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
}
}
}

View File

@ -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.

View File

@ -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(())
}
}

View File

@ -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 }

View File

@ -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;

View File

@ -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;

View File

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

View File

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

View File

@ -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>,
}

View File

@ -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>,
}

View File

@ -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>,
}

View File

@ -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>,
}

View File

@ -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>,
}

View File

@ -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>,
}

View File

@ -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>,
}

View File

@ -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>,
}

View File

@ -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>,
}

View File

@ -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;

View File

@ -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>,
}

View File

@ -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>,
}

View File

@ -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>,
}

View File

@ -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>,
}

View File

@ -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>,
}

View File

@ -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>,
}

View File

@ -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)
}

View File

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

View File

@ -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"

View File

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

View File

@ -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"
}
}

View File

@ -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"

View File

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

View File

@ -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>>,
}

View File

@ -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(())
}
}

View File

@ -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()
}

View File

@ -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();
});
});

View File

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

View File

@ -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"],