Add support for token extensions (#2789)
This commit is contained in:
parent
ae26fd84bb
commit
e3ced784ad
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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",
|
||||
]
|
||||
|
||||
|
|
|
@ -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"] }
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
// waiting for labs to merge
|
|
@ -0,0 +1 @@
|
|||
// waiting for labs to merge
|
|
@ -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>,
|
||||
}
|
|
@ -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>,
|
||||
}
|
|
@ -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>,
|
||||
}
|
|
@ -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>,
|
||||
}
|
|
@ -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>,
|
||||
}
|
|
@ -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>,
|
||||
}
|
|
@ -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>,
|
||||
}
|
|
@ -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>,
|
||||
}
|
|
@ -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>,
|
||||
}
|
|
@ -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;
|
|
@ -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>,
|
||||
}
|
|
@ -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>,
|
||||
}
|
|
@ -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>,
|
||||
}
|
|
@ -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>,
|
||||
}
|
|
@ -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>,
|
||||
}
|
|
@ -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>,
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
"pyth",
|
||||
"realloc",
|
||||
"spl/metadata",
|
||||
"spl/token-extensions",
|
||||
"spl/token-proxy",
|
||||
"spl/token-wrapper",
|
||||
"spl/transfer-hook",
|
||||
|
|
|
@ -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"
|
|
@ -0,0 +1,8 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"programs/*"
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[profile.release]
|
||||
overflow-checks = true
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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"
|
|
@ -0,0 +1,2 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -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>>,
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"types": ["mocha", "chai", "node"],
|
||||
"typeRoots": ["./node_modules/@types"],
|
||||
"lib": ["es2015"],
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
|
@ -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"],
|
||||
|
|
Loading…
Reference in New Issue