From 2d40e68f8a4363b903f70dc07364700eb5adb4ea Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Tue, 8 Oct 2024 21:06:16 +0300 Subject: [PATCH] Add token 2022 --- Cargo.lock | 111 +++- Cargo.toml | 1 + spl/Cargo.toml | 5 +- spl/src/lib.rs | 9 + spl/src/token_2022.rs | 593 ++++++++++++++++++ .../confidential_transfer.rs | 1 + .../confidential_transfer_fee.rs | 1 + spl/src/token_2022_extensions/cpi_guard.rs | 49 ++ .../default_account_state.rs | 58 ++ .../token_2022_extensions/immutable_owner.rs | 24 + .../interest_bearing_mint.rs | 59 ++ .../token_2022_extensions/memo_transfer.rs | 53 ++ .../token_2022_extensions/metadata_pointer.rs | 29 + .../mint_close_authority.rs | 27 + spl/src/token_2022_extensions/mod.rs | 25 + .../token_2022_extensions/non_transferable.rs | 24 + .../permanent_delegate.rs | 27 + spl/src/token_2022_extensions/transfer_fee.rs | 193 ++++++ .../token_2022_extensions/transfer_hook.rs | 59 ++ spl/src/token_interface.rs | 96 +++ 20 files changed, 1431 insertions(+), 13 deletions(-) create mode 100644 spl/src/token_2022.rs create mode 100644 spl/src/token_2022_extensions/confidential_transfer.rs create mode 100644 spl/src/token_2022_extensions/confidential_transfer_fee.rs create mode 100644 spl/src/token_2022_extensions/cpi_guard.rs create mode 100644 spl/src/token_2022_extensions/default_account_state.rs create mode 100644 spl/src/token_2022_extensions/immutable_owner.rs create mode 100644 spl/src/token_2022_extensions/interest_bearing_mint.rs create mode 100644 spl/src/token_2022_extensions/memo_transfer.rs create mode 100644 spl/src/token_2022_extensions/metadata_pointer.rs create mode 100644 spl/src/token_2022_extensions/mint_close_authority.rs create mode 100644 spl/src/token_2022_extensions/mod.rs create mode 100644 spl/src/token_2022_extensions/non_transferable.rs create mode 100644 spl/src/token_2022_extensions/permanent_delegate.rs create mode 100644 spl/src/token_2022_extensions/transfer_fee.rs create mode 100644 spl/src/token_2022_extensions/transfer_hook.rs create mode 100644 spl/src/token_interface.rs diff --git a/Cargo.lock b/Cargo.lock index dc30dee419..bedd62b584 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -299,6 +299,7 @@ dependencies = [ "solana-program", "spl-associated-token-account 1.1.3", "spl-token 3.5.0", + "spl-token-2022 0.7.0", ] [[package]] @@ -4671,7 +4672,20 @@ dependencies = [ "bytemuck", "solana-program", "solana-zk-token-sdk", - "spl-program-error", + "spl-program-error 0.3.0", +] + +[[package]] +name = "spl-program-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af92f74cd3b0fdfda59fef4b571a92123e4df0f67cc43f73163975d31118ef82" +dependencies = [ + "num-derive 0.3.3", + "num-traits", + "solana-program", + "spl-program-error-derive 0.2.0", + "thiserror", ] [[package]] @@ -4683,10 +4697,21 @@ dependencies = [ "num-derive 0.4.1", "num-traits", "solana-program", - "spl-program-error-derive", + "spl-program-error-derive 0.3.1", "thiserror", ] +[[package]] +name = "spl-program-error-derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173f3cc506847882189b3a5b67299f617fed2f9730f122dd197b82e1e213dee5" +dependencies = [ + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", +] + [[package]] name = "spl-program-error-derive" version = "0.3.1" @@ -4699,6 +4724,19 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "spl-tlv-account-resolution" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82149a5a06b5f158d03904066375eaf0c8a2422557cc3d5a25d277260d9a3b16" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator", + "spl-program-error 0.2.0", + "spl-type-length-value 0.2.0", +] + [[package]] name = "spl-tlv-account-resolution" version = "0.4.0" @@ -4709,8 +4747,8 @@ dependencies = [ "solana-program", "spl-discriminator", "spl-pod", - "spl-program-error", - "spl-type-length-value", + "spl-program-error 0.3.0", + "spl-type-length-value 0.3.0", ] [[package]] @@ -4761,6 +4799,25 @@ dependencies = [ "thiserror", ] +[[package]] +name = "spl-token-2022" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b24ac5786a3fefbf59f5606312c61abf87b23154e7a717e5d18216fbea4711db" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive 0.3.3", + "num-traits", + "num_enum 0.6.1", + "solana-program", + "solana-zk-token-sdk", + "spl-memo 3.0.1", + "spl-token 4.0.0", + "spl-transfer-hook-interface 0.1.0", + "thiserror", +] + [[package]] name = "spl-token-2022" version = "0.9.0" @@ -4778,8 +4835,8 @@ dependencies = [ "spl-pod", "spl-token 4.0.0", "spl-token-metadata-interface", - "spl-transfer-hook-interface", - "spl-type-length-value", + "spl-transfer-hook-interface 0.3.0", + "spl-type-length-value 0.3.0", "thiserror", ] @@ -4793,8 +4850,26 @@ dependencies = [ "solana-program", "spl-discriminator", "spl-pod", - "spl-program-error", - "spl-type-length-value", + "spl-program-error 0.3.0", + "spl-type-length-value 0.3.0", +] + +[[package]] +name = "spl-transfer-hook-interface" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a2326852adf88716fbac7f54cd6ee2c8a0b5a14ede24db3b4519c4ff13df04b" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive 0.3.3", + "num-traits", + "num_enum 0.6.1", + "solana-program", + "spl-discriminator", + "spl-tlv-account-resolution 0.2.0", + "spl-type-length-value 0.2.0", + "thiserror", ] [[package]] @@ -4808,9 +4883,21 @@ dependencies = [ "solana-program", "spl-discriminator", "spl-pod", - "spl-program-error", - "spl-tlv-account-resolution", - "spl-type-length-value", + "spl-program-error 0.3.0", + "spl-tlv-account-resolution 0.4.0", + "spl-type-length-value 0.3.0", +] + +[[package]] +name = "spl-type-length-value" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1d085f426b33b8365fb98383d1b8b3925e21bdfe579c851ceaa7f511dbec191" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator", + "spl-program-error 0.2.0", ] [[package]] @@ -4823,7 +4910,7 @@ dependencies = [ "solana-program", "spl-discriminator", "spl-pod", - "spl-program-error", + "spl-program-error 0.3.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ce17ba7cf3..7fb8da6343 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,4 @@ members = [ "lang/syn", "spl", ] +resolver = "2" diff --git a/spl/Cargo.toml b/spl/Cargo.toml index 380ebeb7cc..73f596de68 100644 --- a/spl/Cargo.toml +++ b/spl/Cargo.toml @@ -8,9 +8,11 @@ description = "CPI clients for SPL programs" publish = ["star-atlas"] [features] -default = ["mint", "token", "associated_token"] +default = ["mint", "token", "associated_token", "token_2022", "token_2022_extensions"] mint = [] token = ["spl-token"] +token_2022 = ["spl-token-2022"] +token_2022_extensions = ["spl-token-2022"] associated_token = ["spl-associated-token-account"] governance = [] shmem = [] @@ -25,3 +27,4 @@ serum_dex = { git = "https://github.com/project-serum/serum-dex", rev = "1be91f2 solana-program = ">=1.10.29" spl-associated-token-account = { version = "1.1.0", features = ["no-entrypoint"], optional = true } spl-token = { version = "3.3.0", features = ["no-entrypoint"], optional = true } +spl-token-2022 = { version = "0.7", features = ["no-entrypoint"], optional = true } diff --git a/spl/src/lib.rs b/spl/src/lib.rs index 7ebd662ed3..5922168f4a 100644 --- a/spl/src/lib.rs +++ b/spl/src/lib.rs @@ -9,6 +9,15 @@ pub mod mint; #[cfg(feature = "token")] 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; + #[cfg(feature = "dex")] pub mod dex; diff --git a/spl/src/token_2022.rs b/spl/src/token_2022.rs new file mode 100644 index 0000000000..04b42a9c7a --- /dev/null +++ b/spl/src/token_2022.rs @@ -0,0 +1,593 @@ +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 use spl_token_2022; +pub use spl_token_2022::ID; + +pub fn transfer_checked<'info>( + ctx: CpiContext<'_, '_, '_, 'info, TransferChecked<'info>>, + amount: u64, + decimals: u8, +) -> Result<()> { + let ix = spl_token_2022::instruction::transfer_checked( + ctx.program.key, + ctx.accounts.from.key, + ctx.accounts.mint.key, + ctx.accounts.to.key, + ctx.accounts.authority.key, + &[], + amount, + decimals, + )?; + solana_program::program::invoke_signed( + &ix, + &[ + ctx.accounts.from, + ctx.accounts.mint, + ctx.accounts.to, + ctx.accounts.authority, + ], + ctx.signer_seeds, + ) + .map_err(Into::into) +} + +pub fn mint_to<'info>( + ctx: CpiContext<'_, '_, '_, 'info, MintTo<'info>>, + amount: u64, +) -> Result<()> { + let ix = spl_token_2022::instruction::mint_to( + ctx.program.key, + ctx.accounts.mint.key, + ctx.accounts.to.key, + ctx.accounts.authority.key, + &[], + amount, + )?; + solana_program::program::invoke_signed( + &ix, + &[ctx.accounts.to, ctx.accounts.mint, ctx.accounts.authority], + ctx.signer_seeds, + ) + .map_err(Into::into) +} + +pub fn mint_to_checked<'info>( + ctx: CpiContext<'_, '_, '_, 'info, MintToChecked<'info>>, + amount: u64, + decimals: u8, +) -> Result<()> { + let ix = spl_token_2022::instruction::mint_to_checked( + ctx.program.key, + ctx.accounts.mint.key, + ctx.accounts.to.key, + ctx.accounts.authority.key, + &[], + amount, + decimals, + )?; + solana_program::program::invoke_signed( + &ix, + &[ctx.accounts.to, ctx.accounts.mint, ctx.accounts.authority], + ctx.signer_seeds, + ) + .map_err(Into::into) +} + +pub fn burn<'info>(ctx: CpiContext<'_, '_, '_, 'info, Burn<'info>>, amount: u64) -> Result<()> { + let ix = spl_token_2022::instruction::burn( + ctx.program.key, + ctx.accounts.from.key, + ctx.accounts.mint.key, + ctx.accounts.authority.key, + &[], + amount, + )?; + solana_program::program::invoke_signed( + &ix, + &[ctx.accounts.from, ctx.accounts.mint, ctx.accounts.authority], + ctx.signer_seeds, + ) + .map_err(Into::into) +} + +pub fn burn_checked<'info>( + ctx: CpiContext<'_, '_, '_, 'info, BurnChecked<'info>>, + amount: u64, + decimals: u8, +) -> Result<()> { + let ix = spl_token_2022::instruction::burn_checked( + ctx.program.key, + ctx.accounts.from.key, + ctx.accounts.mint.key, + ctx.accounts.authority.key, + &[], + amount, + decimals, + )?; + solana_program::program::invoke_signed( + &ix, + &[ctx.accounts.from, ctx.accounts.mint, ctx.accounts.authority], + ctx.signer_seeds, + ) + .map_err(Into::into) +} + +pub fn approve<'info>( + ctx: CpiContext<'_, '_, '_, 'info, Approve<'info>>, + amount: u64, +) -> Result<()> { + let ix = spl_token_2022::instruction::approve( + ctx.program.key, + ctx.accounts.to.key, + ctx.accounts.delegate.key, + ctx.accounts.authority.key, + &[], + amount, + )?; + solana_program::program::invoke_signed( + &ix, + &[ + ctx.accounts.to, + ctx.accounts.delegate, + ctx.accounts.authority, + ], + ctx.signer_seeds, + ) + .map_err(Into::into) +} + +pub fn approve_checked<'info>( + ctx: CpiContext<'_, '_, '_, 'info, ApproveChecked<'info>>, + amount: u64, + decimals: u8, +) -> Result<()> { + let ix = spl_token_2022::instruction::approve_checked( + ctx.program.key, + ctx.accounts.to.key, + ctx.accounts.mint.key, + ctx.accounts.delegate.key, + ctx.accounts.authority.key, + &[], + amount, + decimals, + )?; + solana_program::program::invoke_signed( + &ix, + &[ + ctx.accounts.to, + ctx.accounts.mint, + ctx.accounts.delegate, + ctx.accounts.authority, + ], + ctx.signer_seeds, + ) + .map_err(Into::into) +} + +pub fn revoke<'info>(ctx: CpiContext<'_, '_, '_, 'info, Revoke<'info>>) -> Result<()> { + let ix = spl_token_2022::instruction::revoke( + ctx.program.key, + ctx.accounts.source.key, + ctx.accounts.authority.key, + &[], + )?; + solana_program::program::invoke_signed( + &ix, + &[ctx.accounts.source, ctx.accounts.authority], + ctx.signer_seeds, + ) + .map_err(Into::into) +} + +pub fn initialize_account<'info>( + ctx: CpiContext<'_, '_, '_, 'info, InitializeAccount<'info>>, +) -> Result<()> { + let ix = spl_token_2022::instruction::initialize_account( + ctx.program.key, + ctx.accounts.account.key, + ctx.accounts.mint.key, + ctx.accounts.authority.key, + )?; + solana_program::program::invoke( + &ix, + &[ + ctx.accounts.account, + ctx.accounts.mint, + ctx.accounts.authority, + ctx.accounts.rent, + ], + ) + .map_err(Into::into) +} + +pub fn initialize_account3<'info>( + ctx: CpiContext<'_, '_, '_, 'info, InitializeAccount3<'info>>, +) -> Result<()> { + let ix = spl_token_2022::instruction::initialize_account3( + ctx.program.key, + ctx.accounts.account.key, + ctx.accounts.mint.key, + ctx.accounts.authority.key, + )?; + solana_program::program::invoke(&ix, &[ctx.accounts.account, ctx.accounts.mint]) + .map_err(Into::into) +} + +pub fn close_account<'info>(ctx: CpiContext<'_, '_, '_, 'info, CloseAccount<'info>>) -> Result<()> { + let ix = spl_token_2022::instruction::close_account( + ctx.program.key, + ctx.accounts.account.key, + ctx.accounts.destination.key, + ctx.accounts.authority.key, + &[], // TODO: support multisig + )?; + solana_program::program::invoke_signed( + &ix, + &[ + ctx.accounts.account, + ctx.accounts.destination, + ctx.accounts.authority, + ], + ctx.signer_seeds, + ) + .map_err(Into::into) +} + +pub fn freeze_account<'info>( + ctx: CpiContext<'_, '_, '_, 'info, FreezeAccount<'info>>, +) -> Result<()> { + let ix = spl_token_2022::instruction::freeze_account( + ctx.program.key, + ctx.accounts.account.key, + ctx.accounts.mint.key, + ctx.accounts.authority.key, + &[], // TODO: Support multisig signers. + )?; + solana_program::program::invoke_signed( + &ix, + &[ + ctx.accounts.account, + ctx.accounts.mint, + ctx.accounts.authority, + ], + ctx.signer_seeds, + ) + .map_err(Into::into) +} + +pub fn thaw_account<'info>(ctx: CpiContext<'_, '_, '_, 'info, ThawAccount<'info>>) -> Result<()> { + let ix = spl_token_2022::instruction::thaw_account( + ctx.program.key, + ctx.accounts.account.key, + ctx.accounts.mint.key, + ctx.accounts.authority.key, + &[], // TODO: Support multisig signers. + )?; + solana_program::program::invoke_signed( + &ix, + &[ + ctx.accounts.account, + ctx.accounts.mint, + ctx.accounts.authority, + ], + ctx.signer_seeds, + ) + .map_err(Into::into) +} + +pub fn initialize_mint<'info>( + ctx: CpiContext<'_, '_, '_, 'info, InitializeMint<'info>>, + decimals: u8, + authority: &Pubkey, + freeze_authority: Option<&Pubkey>, +) -> Result<()> { + let ix = spl_token_2022::instruction::initialize_mint( + ctx.program.key, + ctx.accounts.mint.key, + authority, + freeze_authority, + decimals, + )?; + solana_program::program::invoke(&ix, &[ctx.accounts.mint, ctx.accounts.rent]) + .map_err(Into::into) +} + +pub fn initialize_mint2<'info>( + ctx: CpiContext<'_, '_, '_, 'info, InitializeMint2<'info>>, + decimals: u8, + authority: &Pubkey, + freeze_authority: Option<&Pubkey>, +) -> Result<()> { + let ix = spl_token_2022::instruction::initialize_mint2( + ctx.program.key, + ctx.accounts.mint.key, + authority, + freeze_authority, + decimals, + )?; + solana_program::program::invoke(&ix, &[ctx.accounts.mint]).map_err(Into::into) +} + +pub fn set_authority<'info>( + ctx: CpiContext<'_, '_, '_, 'info, SetAuthority<'info>>, + authority_type: spl_token_2022::instruction::AuthorityType, + new_authority: Option, +) -> Result<()> { + let mut spl_new_authority: Option<&Pubkey> = None; + if new_authority.is_some() { + spl_new_authority = new_authority.as_ref() + } + + let ix = spl_token_2022::instruction::set_authority( + ctx.program.key, + ctx.accounts.account_or_mint.key, + spl_new_authority, + authority_type, + ctx.accounts.current_authority.key, + &[], // TODO: Support multisig signers. + )?; + solana_program::program::invoke_signed( + &ix, + &[ctx.accounts.account_or_mint, ctx.accounts.current_authority], + ctx.signer_seeds, + ) + .map_err(Into::into) +} + +pub fn sync_native<'info>(ctx: CpiContext<'_, '_, '_, 'info, SyncNative<'info>>) -> Result<()> { + let ix = spl_token_2022::instruction::sync_native(ctx.program.key, ctx.accounts.account.key)?; + solana_program::program::invoke(&ix, &[ctx.accounts.account]).map_err(Into::into) +} + +pub fn get_account_data_size<'info>( + ctx: CpiContext<'_, '_, '_, 'info, GetAccountDataSize<'info>>, + extension_types: &[spl_token_2022::extension::ExtensionType], +) -> Result { + let ix = spl_token_2022::instruction::get_account_data_size( + ctx.program.key, + ctx.accounts.mint.key, + extension_types, + )?; + solana_program::program::invoke(&ix, &[ctx.accounts.mint])?; + solana_program::program::get_return_data() + .ok_or(solana_program::program_error::ProgramError::InvalidInstructionData) + .and_then(|(key, data)| { + if key != *ctx.program.key { + Err(solana_program::program_error::ProgramError::IncorrectProgramId) + } else { + data.try_into().map(u64::from_le_bytes).map_err(|_| { + solana_program::program_error::ProgramError::InvalidInstructionData + }) + } + }) + .map_err(Into::into) +} + +pub fn initialize_mint_close_authority<'info>( + ctx: CpiContext<'_, '_, '_, 'info, InitializeMintCloseAuthority<'info>>, + close_authority: Option<&Pubkey>, +) -> Result<()> { + let ix = spl_token_2022::instruction::initialize_mint_close_authority( + ctx.program.key, + ctx.accounts.mint.key, + close_authority, + )?; + solana_program::program::invoke(&ix, &[ctx.accounts.mint]).map_err(Into::into) +} + +pub fn initialize_immutable_owner<'info>( + ctx: CpiContext<'_, '_, '_, 'info, InitializeImmutableOwner<'info>>, +) -> Result<()> { + let ix = spl_token_2022::instruction::initialize_immutable_owner( + ctx.program.key, + ctx.accounts.account.key, + )?; + solana_program::program::invoke(&ix, &[ctx.accounts.account]).map_err(Into::into) +} + +pub fn amount_to_ui_amount<'info>( + ctx: CpiContext<'_, '_, '_, 'info, AmountToUiAmount<'info>>, + amount: u64, +) -> Result { + let ix = spl_token_2022::instruction::amount_to_ui_amount( + ctx.program.key, + ctx.accounts.account.key, + amount, + )?; + solana_program::program::invoke(&ix, &[ctx.accounts.account])?; + solana_program::program::get_return_data() + .ok_or(solana_program::program_error::ProgramError::InvalidInstructionData) + .and_then(|(key, data)| { + if key != *ctx.program.key { + Err(solana_program::program_error::ProgramError::IncorrectProgramId) + } else { + String::from_utf8(data).map_err(|_| { + solana_program::program_error::ProgramError::InvalidInstructionData + }) + } + }) + .map_err(Into::into) +} + +pub fn ui_amount_to_amount<'info>( + ctx: CpiContext<'_, '_, '_, 'info, UiAmountToAmount<'info>>, + ui_amount: &str, +) -> Result { + let ix = spl_token_2022::instruction::ui_amount_to_amount( + ctx.program.key, + ctx.accounts.account.key, + ui_amount, + )?; + solana_program::program::invoke(&ix, &[ctx.accounts.account])?; + solana_program::program::get_return_data() + .ok_or(solana_program::program_error::ProgramError::InvalidInstructionData) + .and_then(|(key, data)| { + if key != *ctx.program.key { + Err(solana_program::program_error::ProgramError::IncorrectProgramId) + } else { + data.try_into().map(u64::from_le_bytes).map_err(|_| { + solana_program::program_error::ProgramError::InvalidInstructionData + }) + } + }) + .map_err(Into::into) +} + +#[derive(Accounts)] +pub struct Transfer<'info> { + pub from: AccountInfo<'info>, + pub to: AccountInfo<'info>, + pub authority: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct TransferChecked<'info> { + pub from: AccountInfo<'info>, + pub mint: AccountInfo<'info>, + pub to: AccountInfo<'info>, + pub authority: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct MintTo<'info> { + pub mint: AccountInfo<'info>, + pub to: AccountInfo<'info>, + pub authority: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct MintToChecked<'info> { + pub mint: AccountInfo<'info>, + pub to: AccountInfo<'info>, + pub authority: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct Burn<'info> { + pub mint: AccountInfo<'info>, + pub from: AccountInfo<'info>, + pub authority: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct BurnChecked<'info> { + pub mint: AccountInfo<'info>, + pub from: AccountInfo<'info>, + pub authority: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct Approve<'info> { + pub to: AccountInfo<'info>, + pub delegate: AccountInfo<'info>, + pub authority: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct ApproveChecked<'info> { + pub to: AccountInfo<'info>, + pub mint: AccountInfo<'info>, + pub delegate: AccountInfo<'info>, + pub authority: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct Revoke<'info> { + pub source: AccountInfo<'info>, + pub authority: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct InitializeAccount<'info> { + pub account: AccountInfo<'info>, + pub mint: AccountInfo<'info>, + pub authority: AccountInfo<'info>, + pub rent: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct InitializeAccount3<'info> { + pub account: AccountInfo<'info>, + pub mint: AccountInfo<'info>, + pub authority: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct CloseAccount<'info> { + pub account: AccountInfo<'info>, + pub destination: AccountInfo<'info>, + pub authority: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct FreezeAccount<'info> { + pub account: AccountInfo<'info>, + pub mint: AccountInfo<'info>, + pub authority: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct ThawAccount<'info> { + pub account: AccountInfo<'info>, + pub mint: AccountInfo<'info>, + pub authority: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct InitializeMint<'info> { + pub mint: AccountInfo<'info>, + pub rent: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct InitializeMint2<'info> { + pub mint: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct SetAuthority<'info> { + pub current_authority: AccountInfo<'info>, + pub account_or_mint: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct SyncNative<'info> { + pub account: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct GetAccountDataSize<'info> { + pub mint: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct InitializeMintCloseAuthority<'info> { + pub mint: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct InitializeImmutableOwner<'info> { + pub account: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct AmountToUiAmount<'info> { + pub account: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct UiAmountToAmount<'info> { + pub account: AccountInfo<'info>, +} + +#[derive(Clone)] +pub struct Token2022; + +impl anchor_lang::Id for Token2022 { + fn id() -> Pubkey { + ID + } +} diff --git a/spl/src/token_2022_extensions/confidential_transfer.rs b/spl/src/token_2022_extensions/confidential_transfer.rs new file mode 100644 index 0000000000..665ccd2cfc --- /dev/null +++ b/spl/src/token_2022_extensions/confidential_transfer.rs @@ -0,0 +1 @@ +// waiting for labs to merge diff --git a/spl/src/token_2022_extensions/confidential_transfer_fee.rs b/spl/src/token_2022_extensions/confidential_transfer_fee.rs new file mode 100644 index 0000000000..665ccd2cfc --- /dev/null +++ b/spl/src/token_2022_extensions/confidential_transfer_fee.rs @@ -0,0 +1 @@ +// waiting for labs to merge diff --git a/spl/src/token_2022_extensions/cpi_guard.rs b/spl/src/token_2022_extensions/cpi_guard.rs new file mode 100644 index 0000000000..c6d31dd481 --- /dev/null +++ b/spl/src/token_2022_extensions/cpi_guard.rs @@ -0,0 +1,49 @@ +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>, +} diff --git a/spl/src/token_2022_extensions/default_account_state.rs b/spl/src/token_2022_extensions/default_account_state.rs new file mode 100644 index 0000000000..90f59ecb97 --- /dev/null +++ b/spl/src/token_2022_extensions/default_account_state.rs @@ -0,0 +1,58 @@ +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>, +} diff --git a/spl/src/token_2022_extensions/immutable_owner.rs b/spl/src/token_2022_extensions/immutable_owner.rs new file mode 100644 index 0000000000..ceea103c96 --- /dev/null +++ b/spl/src/token_2022_extensions/immutable_owner.rs @@ -0,0 +1,24 @@ +use anchor_lang::Result; +use anchor_lang::{context::CpiContext, Accounts}; +use solana_program::account_info::AccountInfo; + +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>, +} diff --git a/spl/src/token_2022_extensions/interest_bearing_mint.rs b/spl/src/token_2022_extensions/interest_bearing_mint.rs new file mode 100644 index 0000000000..16e099577b --- /dev/null +++ b/spl/src/token_2022_extensions/interest_bearing_mint.rs @@ -0,0 +1,59 @@ +use anchor_lang::Result; +use anchor_lang::{context::CpiContext, Accounts}; +use solana_program::account_info::AccountInfo; +use solana_program::pubkey::Pubkey; + +pub fn interest_bearing_mint_initialize<'info>( + ctx: CpiContext<'_, '_, '_, 'info, InterestBearingMintInitialize<'info>>, + rate_authority: Option, + 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>, +} diff --git a/spl/src/token_2022_extensions/memo_transfer.rs b/spl/src/token_2022_extensions/memo_transfer.rs new file mode 100644 index 0000000000..0306cbe448 --- /dev/null +++ b/spl/src/token_2022_extensions/memo_transfer.rs @@ -0,0 +1,53 @@ +use anchor_lang::Result; +use anchor_lang::{context::CpiContext, Accounts}; +use solana_program::account_info::AccountInfo; + +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>, +} diff --git a/spl/src/token_2022_extensions/metadata_pointer.rs b/spl/src/token_2022_extensions/metadata_pointer.rs new file mode 100644 index 0000000000..f459644471 --- /dev/null +++ b/spl/src/token_2022_extensions/metadata_pointer.rs @@ -0,0 +1,29 @@ +use anchor_lang::solana_program::account_info::AccountInfo; +use anchor_lang::solana_program::pubkey::Pubkey; +use anchor_lang::Result; +use anchor_lang::{context::CpiContext, Accounts}; + +pub fn metadata_pointer_initialize<'info>( + ctx: CpiContext<'_, '_, '_, 'info, MetadataPointerInitialize<'info>>, + authority: Option, + metadata_address: Option, +) -> 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>, +} diff --git a/spl/src/token_2022_extensions/mint_close_authority.rs b/spl/src/token_2022_extensions/mint_close_authority.rs new file mode 100644 index 0000000000..3734747dc4 --- /dev/null +++ b/spl/src/token_2022_extensions/mint_close_authority.rs @@ -0,0 +1,27 @@ +use anchor_lang::solana_program::account_info::AccountInfo; +use anchor_lang::solana_program::pubkey::Pubkey; +use anchor_lang::Result; +use anchor_lang::{context::CpiContext, Accounts}; + +pub fn mint_close_authority_initialize<'info>( + ctx: CpiContext<'_, '_, '_, 'info, MintCloseAuthorityInitialize<'info>>, + authority: Option<&Pubkey>, +) -> Result<()> { + let ix = spl_token_2022::instruction::initialize_mint_close_authority( + ctx.accounts.token_program_id.key, + ctx.accounts.mint.key, + authority, + )?; + solana_program::program::invoke_signed( + &ix, + &[ctx.accounts.token_program_id, ctx.accounts.mint], + ctx.signer_seeds, + ) + .map_err(Into::into) +} + +#[derive(Accounts)] +pub struct MintCloseAuthorityInitialize<'info> { + pub token_program_id: AccountInfo<'info>, + pub mint: AccountInfo<'info>, +} diff --git a/spl/src/token_2022_extensions/mod.rs b/spl/src/token_2022_extensions/mod.rs new file mode 100644 index 0000000000..bf7b8a5cd5 --- /dev/null +++ b/spl/src/token_2022_extensions/mod.rs @@ -0,0 +1,25 @@ +pub mod confidential_transfer; +pub mod confidential_transfer_fee; +pub mod cpi_guard; +pub mod default_account_state; +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 transfer_fee; +pub mod transfer_hook; + +pub use cpi_guard::*; +pub use default_account_state::*; +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 transfer_fee::*; +pub use transfer_hook::*; diff --git a/spl/src/token_2022_extensions/non_transferable.rs b/spl/src/token_2022_extensions/non_transferable.rs new file mode 100644 index 0000000000..ffada98d66 --- /dev/null +++ b/spl/src/token_2022_extensions/non_transferable.rs @@ -0,0 +1,24 @@ +use anchor_lang::Result; +use anchor_lang::{context::CpiContext, Accounts}; +use solana_program::account_info::AccountInfo; + +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>, +} diff --git a/spl/src/token_2022_extensions/permanent_delegate.rs b/spl/src/token_2022_extensions/permanent_delegate.rs new file mode 100644 index 0000000000..5930bd3ad5 --- /dev/null +++ b/spl/src/token_2022_extensions/permanent_delegate.rs @@ -0,0 +1,27 @@ +use anchor_lang::Result; +use anchor_lang::{context::CpiContext, Accounts}; +use solana_program::account_info::AccountInfo; +use solana_program::pubkey::Pubkey; + +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>, +} diff --git a/spl/src/token_2022_extensions/transfer_fee.rs b/spl/src/token_2022_extensions/transfer_fee.rs new file mode 100644 index 0000000000..3ca3375107 --- /dev/null +++ b/spl/src/token_2022_extensions/transfer_fee.rs @@ -0,0 +1,193 @@ +use anchor_lang::Result; +use anchor_lang::{context::CpiContext, Accounts}; +use solana_program::account_info::AccountInfo; +use solana_program::pubkey::Pubkey; + +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>, +) -> 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::>().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>, +} + +pub fn withdraw_withheld_tokens_from_accounts<'info>( + ctx: CpiContext<'_, '_, '_, 'info, WithdrawWithheldTokensFromAccounts<'info>>, + sources: Vec>, +) -> Result<()> { + let ix = spl_token_2022::extension::transfer_fee::instruction::withdraw_withheld_tokens_from_accounts( + ctx.accounts.token_program_id.key, + ctx.accounts.mint.key, + ctx.accounts.destination.key, + ctx.accounts.authority.key, + &[], + sources.iter().map(|a| a.key).collect::>().as_slice(), + )?; + + let mut account_infos = vec![ + ctx.accounts.token_program_id, + ctx.accounts.mint, + ctx.accounts.destination, + ctx.accounts.authority, + ]; + 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 WithdrawWithheldTokensFromAccounts<'info> { + pub token_program_id: AccountInfo<'info>, + pub mint: AccountInfo<'info>, + pub destination: AccountInfo<'info>, + pub authority: AccountInfo<'info>, +} diff --git a/spl/src/token_2022_extensions/transfer_hook.rs b/spl/src/token_2022_extensions/transfer_hook.rs new file mode 100644 index 0000000000..56897dacc9 --- /dev/null +++ b/spl/src/token_2022_extensions/transfer_hook.rs @@ -0,0 +1,59 @@ +use anchor_lang::Result; +use anchor_lang::{context::CpiContext, Accounts}; +use solana_program::account_info::AccountInfo; +use solana_program::pubkey::Pubkey; + +pub fn transfer_hook_initialize<'info>( + ctx: CpiContext<'_, '_, '_, 'info, TransferHookInitialize<'info>>, + authority: Option, + transfer_hook_program_id: Option, +) -> 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, +) -> 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>, +} diff --git a/spl/src/token_interface.rs b/spl/src/token_interface.rs new file mode 100644 index 0000000000..e5a2aa194f --- /dev/null +++ b/spl/src/token_interface.rs @@ -0,0 +1,96 @@ +use anchor_lang::solana_program::program_pack::Pack; +use anchor_lang::solana_program::pubkey::Pubkey; +use spl_token_2022::{ + extension::{BaseStateWithExtensions, Extension, StateWithExtensions, ExtensionType}, + solana_zk_token_sdk::instruction::Pod, +}; +use std::ops::Deref; + +pub use crate::token_2022::*; + +static IDS: [Pubkey; 2] = [spl_token::ID, spl_token_2022::ID]; + +#[derive(Clone, Debug, Default, PartialEq, Copy)] +pub struct TokenAccount(spl_token_2022::state::Account); + +impl anchor_lang::AccountDeserialize for TokenAccount { + fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result { + StateWithExtensions::::unpack(buf) + .map(|t| TokenAccount(t.base)) + .map_err(Into::into) + } +} + +impl anchor_lang::AccountSerialize for TokenAccount {} + +impl anchor_lang::Owners for TokenAccount { + fn owners() -> &'static [Pubkey] { + &IDS + } +} + +impl Deref for TokenAccount { + type Target = spl_token_2022::state::Account; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Clone, Debug, Default, PartialEq, Copy)] +pub struct Mint(spl_token_2022::state::Mint); + +impl anchor_lang::AccountDeserialize for Mint { + fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result { + StateWithExtensions::::unpack(buf) + .map(|t| Mint(t.base)) + .map_err(Into::into) + } +} + +impl anchor_lang::AccountSerialize for Mint {} + +impl anchor_lang::Owners for Mint { + fn owners() -> &'static [Pubkey] { + &IDS + } +} + +impl Deref for Mint { + type Target = spl_token_2022::state::Mint; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Clone)] +pub struct TokenInterface; + +impl anchor_lang::Ids for TokenInterface { + fn ids() -> &'static [Pubkey] { + &IDS + } +} + +pub type ExtensionsVec = Vec; + +pub fn find_mint_account_size(extensions: Option<&ExtensionsVec>) -> anchor_lang::Result { + if let Some(extensions) = extensions { + Ok(ExtensionType::get_account_len::< + spl_token_2022::state::Mint, + >(extensions)) + } else { + Ok(spl_token_2022::state::Mint::LEN) + } +} + +pub fn get_mint_extension_data( + account: &solana_program::account_info::AccountInfo, +) -> anchor_lang::Result { + let mint_data = account.data.borrow(); + let mint_with_extension = + StateWithExtensions::::unpack(&mint_data)?; + let extension_data = *mint_with_extension.get_extension::()?; + Ok(extension_data) +}