Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
905f41e
Permissioned Burn Extension
Szegoo Oct 31, 2025
f0175dc
compiles
Szegoo Oct 31, 2025
ea637bd
handle permissioned burn in processor
Szegoo Oct 31, 2025
d918f27
cleanup
Szegoo Oct 31, 2025
6a2e5bd
typo
Szegoo Oct 31, 2025
21b2e65
fmt
Szegoo Oct 31, 2025
e5ca5d4
remove unnecessary PermissionedBurnAccount
Szegoo Nov 1, 2025
d533794
Update interface/src/extension/mod.rs
Szegoo Nov 4, 2025
c22a5fe
add missing unpack & change index to 46
Szegoo Nov 4, 2025
f9133d3
implement authority type
Szegoo Nov 5, 2025
52931da
new PermissionedBurn instruction
Szegoo Nov 6, 2025
8a04278
small fix & add to rust-legacy
Szegoo Nov 8, 2025
05ee8e0
remove
Szegoo Nov 8, 2025
88b3d0b
move instruction under PermissionedBurnExtension
Szegoo Nov 26, 2025
32baa70
refactor
Szegoo Nov 26, 2025
c062644
rust-legacy test
Szegoo Nov 27, 2025
1cd3ce6
clean up & fix
Szegoo Nov 28, 2025
56e1a93
more cleanup
Szegoo Nov 28, 2025
d51e9dd
js-legacy test
Szegoo Nov 28, 2025
1f8cba9
add to cli
Szegoo Nov 28, 2025
4d7f325
add to js-client
Szegoo Nov 29, 2025
0aa93da
change order & separate enums
Szegoo Dec 5, 2025
7d9cea1
leftover ordering update & authority update test
Szegoo Dec 5, 2025
1adec9d
--permissioned-burn-authority
Szegoo Dec 5, 2025
ccc0238
handle None authority
Szegoo Dec 10, 2025
a6e910b
test None
Szegoo Dec 10, 2025
7970ee9
format & make clippy happy
Szegoo Dec 10, 2025
81b5af7
generate clients
Szegoo Dec 10, 2025
0df5cd2
Merge branch 'main' into permissioned-burn
Szegoo Dec 17, 2025
fa8911b
Update clients/cli/src/clap_app.rs
Szegoo Dec 17, 2025
789a206
fixes
Szegoo Dec 17, 2025
b36ebfc
fixes
Szegoo Dec 21, 2025
96414d2
fix nits
Szegoo Jan 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions clients/rust-legacy/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ use {
self, ConfidentialTransferFeeAmount, ConfidentialTransferFeeConfig,
},
cpi_guard, default_account_state, group_member_pointer, group_pointer,
interest_bearing_mint, memo_transfer, metadata_pointer, pausable, scaled_ui_amount,
transfer_fee, transfer_hook, BaseStateWithExtensions, Extension, ExtensionType,
StateWithExtensionsOwned,
interest_bearing_mint, memo_transfer, metadata_pointer, pausable, permissioned_burn,
scaled_ui_amount, transfer_fee, transfer_hook, BaseStateWithExtensions, Extension,
ExtensionType, StateWithExtensionsOwned,
},
instruction,
solana_zk_sdk::{
Expand Down Expand Up @@ -201,6 +201,9 @@ pub enum ExtensionInitializationParams {
PausableConfig {
authority: Pubkey,
},
PermissionedBurnConfig {
authority: Pubkey,
},
ConfidentialMintBurn {
supply_elgamal_pubkey: PodElGamalPubkey,
decryptable_supply: PodAeCiphertext,
Expand All @@ -226,6 +229,7 @@ impl ExtensionInitializationParams {
Self::GroupMemberPointer { .. } => ExtensionType::GroupMemberPointer,
Self::ScaledUiAmountConfig { .. } => ExtensionType::ScaledUiAmount,
Self::PausableConfig { .. } => ExtensionType::Pausable,
Self::PermissionedBurnConfig { .. } => ExtensionType::PermissionedBurn,
Self::ConfidentialMintBurn { .. } => ExtensionType::ConfidentialMintBurn,
}
}
Expand Down Expand Up @@ -348,6 +352,9 @@ impl ExtensionInitializationParams {
Self::PausableConfig { authority } => {
pausable::instruction::initialize(token_program_id, mint, &authority)
}
Self::PermissionedBurnConfig { authority } => {
permissioned_burn::instruction::initialize(token_program_id, mint, &authority)
}
Self::ConfidentialMintBurn {
supply_elgamal_pubkey,
decryptable_supply,
Expand Down
9 changes: 8 additions & 1 deletion interface/src/extension/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use {
non_transferable::{NonTransferable, NonTransferableAccount},
pausable::{PausableAccount, PausableConfig},
permanent_delegate::PermanentDelegate,
permissioned_burn::PermissionedBurnConfig,
scaled_ui_amount::ScaledUiAmountConfig,
transfer_fee::{TransferFeeAmount, TransferFeeConfig},
transfer_hook::{TransferHook, TransferHookAccount},
Expand Down Expand Up @@ -76,6 +77,8 @@ pub mod non_transferable;
pub mod pausable;
/// Permanent Delegate extension
pub mod permanent_delegate;
/// Permissioned burn extension
pub mod permissioned_burn;
/// Scaled UI Amount extension
pub mod scaled_ui_amount;
/// Token-group extension
Expand Down Expand Up @@ -1119,6 +1122,8 @@ pub enum ExtensionType {
Pausable,
/// Indicates that the account belongs to a pausable mint
PausableAccount,
/// Tokens burning requires approval from authorirty.
PermissionedBurn,

/// Test variable-length mint extension
#[cfg(test)]
Expand Down Expand Up @@ -1204,6 +1209,7 @@ impl ExtensionType {
ExtensionType::ScaledUiAmount => pod_get_packed_len::<ScaledUiAmountConfig>(),
ExtensionType::Pausable => pod_get_packed_len::<PausableConfig>(),
ExtensionType::PausableAccount => pod_get_packed_len::<PausableAccount>(),
ExtensionType::PermissionedBurn => pod_get_packed_len::<PermissionedBurnConfig>(),
#[cfg(test)]
ExtensionType::AccountPaddingTest => pod_get_packed_len::<AccountPaddingTest>(),
#[cfg(test)]
Expand Down Expand Up @@ -1270,7 +1276,8 @@ impl ExtensionType {
| ExtensionType::ConfidentialMintBurn
| ExtensionType::TokenGroupMember
| ExtensionType::ScaledUiAmount
| ExtensionType::Pausable => AccountType::Mint,
| ExtensionType::Pausable
| ExtensionType::PermissionedBurn => AccountType::Mint,
ExtensionType::ImmutableOwner
| ExtensionType::TransferFeeAmount
| ExtensionType::ConfidentialTransferAccount
Expand Down
59 changes: 59 additions & 0 deletions interface/src/extension/permissioned_burn/instruction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use {
crate::{
check_program_account,
instruction::{encode_instruction, TokenInstruction},
},
bytemuck::{Pod, Zeroable},
num_enum::{IntoPrimitive, TryFromPrimitive},
solana_instruction::{AccountMeta, Instruction},
solana_program_error::ProgramError,
solana_pubkey::Pubkey,
};

/// Permissioned Burn extension instructions
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, Debug, PartialEq, IntoPrimitive, TryFromPrimitive)]
#[repr(u8)]
pub enum PermissionedBurnInstruction {
/// Require permissioned burn for the given mint account
///
/// Accounts expected by this instruction:
///
/// 0. `[writable]` The mint account to initialize.
///
/// Data expected by this instruction:
/// `crate::extension::permissioned_burn::instruction::InitializeInstructionData`
Initialize,
}

/// Data expected by `PermissionedBurnInstruction::Initialize`
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct InitializeInstructionData {
/// The public key for the account that is required for token burning.
pub authority: Pubkey,
}

/// Create an `Initialize` instruction
pub fn initialize(
token_program_id: &Pubkey,
mint: &Pubkey,
authority: &Pubkey,
) -> Result<Instruction, ProgramError> {
check_program_account(token_program_id)?;
let accounts = vec![AccountMeta::new(*mint, false)];
Ok(encode_instruction(
token_program_id,
accounts,
TokenInstruction::PermissionedBurnExtension,
PermissionedBurnInstruction::Initialize,
&InitializeInstructionData {
authority: *authority,
},
))
}
24 changes: 24 additions & 0 deletions interface/src/extension/permissioned_burn/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use {
crate::extension::{Extension, ExtensionType},
bytemuck::{Pod, Zeroable},
spl_pod::optional_keys::OptionalNonZeroPubkey,
};

/// Instruction types for the permissioned burn extension
pub mod instruction;

/// Indicates that the tokens from this mint require permissioned burn
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
#[repr(C)]
pub struct PermissionedBurnConfig {
/// Authority that is required for burning
pub authority: OptionalNonZeroPubkey,
}

impl Extension for PermissionedBurnConfig {
const TYPE: ExtensionType = ExtensionType::PermissionedBurn;
}
10 changes: 10 additions & 0 deletions interface/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,8 @@ pub enum TokenInstruction<'a> {
ScaledUiAmountExtension,
/// Instruction prefix for instructions to the pausable extension
PausableExtension,
/// Instruction prefix for instructions to the permissioned burn extension
PermissionedBurnExtension,
}
impl<'a> TokenInstruction<'a> {
/// Unpacks a byte buffer into a
Expand Down Expand Up @@ -873,6 +875,7 @@ impl<'a> TokenInstruction<'a> {
42 => Self::ConfidentialMintBurnExtension,
43 => Self::ScaledUiAmountExtension,
44 => Self::PausableExtension,
46 => Self::PermissionedBurnExtension,
_ => return Err(TokenError::InvalidInstruction.into()),
})
}
Expand Down Expand Up @@ -1053,6 +1056,9 @@ impl<'a> TokenInstruction<'a> {
&Self::PausableExtension => {
buf.push(44);
}
&Self::PermissionedBurnExtension => {
buf.push(46);
}
};
buf
}
Expand Down Expand Up @@ -1154,6 +1160,8 @@ pub enum AuthorityType {
ScaledUiAmount,
/// Authority to pause or resume minting / transferring / burning
Pause,
/// Authority to perform a permissioned token burn
PermissionedBurn,
}

impl AuthorityType {
Expand All @@ -1176,6 +1184,7 @@ impl AuthorityType {
AuthorityType::GroupMemberPointer => 14,
AuthorityType::ScaledUiAmount => 15,
AuthorityType::Pause => 16,
AuthorityType::PermissionedBurn => 17,
}
}

Expand All @@ -1199,6 +1208,7 @@ impl AuthorityType {
14 => Ok(AuthorityType::GroupMemberPointer),
15 => Ok(AuthorityType::ScaledUiAmount),
16 => Ok(AuthorityType::Pause),
17 => Ok(AuthorityType::PermissionedBurn),
_ => Err(TokenError::InvalidInstruction.into()),
}
}
Expand Down
2 changes: 2 additions & 0 deletions program/src/extension/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ pub mod non_transferable;
pub mod pausable;
/// Permanent Delegate extension
pub mod permanent_delegate;
/// Permissioned burn extension
pub mod permissioned_burn;
/// Utility to reallocate token accounts
pub mod reallocate;
/// Scaled UI Amount extension
Expand Down
5 changes: 5 additions & 0 deletions program/src/extension/permissioned_burn/instruction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#![deprecated(
since = "9.1.0",
note = "Use spl_token_2022_interface instead and remove spl_token_2022 as a dependency"
)]
pub use spl_token_2022_interface::extension::permissioned_burn::instruction::*;
10 changes: 10 additions & 0 deletions program/src/extension/permissioned_burn/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// Instruction types for the permissioned burn extension
pub mod instruction;
/// Instruction processor for the permissioned burn extension
pub mod processor;

#[deprecated(
since = "9.1.0",
note = "Use spl_token_2022_interface instead and remove spl_token_2022 as a dependency"
)]
pub use spl_token_2022_interface::extension::permissioned_burn::PermissionedBurnConfig;
50 changes: 50 additions & 0 deletions program/src/extension/permissioned_burn/processor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use {
solana_account_info::{next_account_info, AccountInfo},
solana_msg::msg,
solana_program_error::ProgramResult,
solana_pubkey::Pubkey,
spl_token_2022_interface::{
check_program_account,
extension::{
permissioned_burn::{
instruction::{InitializeInstructionData, PermissionedBurnInstruction},
PermissionedBurnConfig,
},
BaseStateWithExtensionsMut, PodStateWithExtensionsMut,
},
instruction::{decode_instruction_data, decode_instruction_type},
pod::PodMint,
},
};

fn process_initialize(
_program_id: &Pubkey,
accounts: &[AccountInfo],
authority: &Pubkey,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let mint_account_info = next_account_info(account_info_iter)?;
let mut mint_data = mint_account_info.data.borrow_mut();
let mut mint = PodStateWithExtensionsMut::<PodMint>::unpack_uninitialized(&mut mint_data)?;

let extension = mint.init_extension::<PermissionedBurnConfig>(true)?;
extension.authority = Some(*authority).try_into()?;

Ok(())
}

pub(crate) fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
input: &[u8],
) -> ProgramResult {
check_program_account(program_id)?;

match decode_instruction_type(input)? {
PermissionedBurnInstruction::Initialize => {
msg!("PermissionedBurnInstruction::Initialize");
let InitializeInstructionData { authority } = decode_instruction_data(input)?;
process_initialize(program_id, accounts, authority)
}
}
}
3 changes: 3 additions & 0 deletions program/src/pod_instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ pub(crate) enum PodTokenInstruction {
ConfidentialMintBurnExtension,
ScaledUiAmountExtension,
PausableExtension,
// 45
PermissionedBurnExtension,
PermissionedBurn,
}

fn unpack_pubkey_option(input: &[u8]) -> Result<PodCOption<Pubkey>, ProgramError> {
Expand Down
Loading