Cos'è il Burn con Permesso?
L'estensione mint PermissionedBurnConfig del Token Extension Program
richiede che un'autorità di burn configurata co-firmi ogni burn per il mint.
Finché l'autorità di burn è impostata:
- Le istruzioni standard
BurneBurnCheckedfalliscono conTokenError::InvalidInstruction. - I burn devono utilizzare
PermissionedBurnInstruction::BurnoPermissionedBurnInstruction::BurnChecked, firmati sia dall'autorità di burn che dal proprietario o delegato del token account.
L'autorità di burn è un co-firmatario, non un sostituto del proprietario. L'autorità da sola non può bruciare token dal token account di qualcun altro, e il proprietario di un token account non può bruciare senza la firma dell'autorità. Un delegato permanente deve anch'esso utilizzare le istruzioni di burn con permesso e necessita comunque della co-firma dell'autorità di burn.
Questo consente a un emittente di mantenere la supply dei token sincronizzata con un registro off-chain, ad esempio un asset tokenizzato che deve rimanere coperto 1:1, impedendo ai detentori di bruciare i token unilateralmente.
L'autorità di burn può essere ruotata in seguito con SetAuthority usando
AuthorityType::PermissionedBurn. Impostare l'autorità su None disabilita
il burn con permesso e riabilita le istruzioni di burn standard. I dati
dell'estensione rimangono comunque sul mint.
Disponibilità
Il burn con permesso è incluso nella
program@v11.0.0
Token-2022. È disponibile oggi su devnet ed è prevista l'abilitazione su
mainnet a giugno 2026. Il Token Extension Program è distribuito separatamente
su ogni cluster, quindi verifica che il deployment sul cluster di destinazione
includa la v11.0.0 o successiva.
Un validator di test locale include anche una versione precedente di Token-2022, quindi per eseguire gli esempi in questa pagina, carica il programma v11.0.0 da devnet nel tuo validator di test:
solana program dump -u devnet TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb token_2022.sosolana-test-validator --reset --bpf-program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb token_2022.so
Come creare un Mint e un Burn con permesso
Per creare un mint con burn con permesso e bruciare token:
- Calcola la dimensione del mint account e il rent necessario per il mint e
l'estensione
PermissionedBurnConfig. - Crea il mint account con
CreateAccount, inizializzaPermissionedBurnConfige inizializza il mint conInitializeMint. - Crea un token account e conia token.
- Esegui il burn con
PermissionedBurnInstruction::BurnCheckedfirmato sia dal proprietario del token account che dall'autorità di burn. - Facoltativamente, disabilita il burn con permesso tramite
SetAuthorityusandoAuthorityType::PermissionedBurne un'autoritàNone, che riabilita le istruzioni di burn standard. Gli esempi di codice completi qui sotto mostrano questo passaggio.
Calcola la dimensione dell'account
Calcola la dimensione del mint account per il mint base più l'estensione
PermissionedBurnConfig. Questa è la dimensione utilizzata in
CreateAccount.
const client = await createClient().use(generatedPayer()).use(solanaRpc({rpcUrl: "http://localhost:8899",rpcSubscriptionsUrl: "ws://localhost:8900"})).use(rpcAirdrop()).use(airdropPayer(lamports(1_000_000_000n)));const mint = await generateKeyPairSigner();const burnAuthority = await generateKeyPairSigner();const permissionedBurnExtension = extension("PermissionedBurn", {authority: burnAuthority.address});const mintSpace = BigInt(getMintSize([permissionedBurnExtension]));
Calcola il rent
Calcola il rent utilizzando la dimensione necessaria per il mint più
l'estensione PermissionedBurnConfig.
const client = await createClient().use(generatedPayer()).use(solanaRpc({rpcUrl: "http://localhost:8899",rpcSubscriptionsUrl: "ws://localhost:8900"})).use(rpcAirdrop()).use(airdropPayer(lamports(1_000_000_000n)));const mint = await generateKeyPairSigner();const burnAuthority = await generateKeyPairSigner();const permissionedBurnExtension = extension("PermissionedBurn", {authority: burnAuthority.address});const mintSpace = BigInt(getMintSize([permissionedBurnExtension]));const mintRent = await client.rpc.getMinimumBalanceForRentExemption(mintSpace).send();
Crea il mint account
Crea il mint account con lo spazio e i lamport calcolati.
const client = await createClient().use(generatedPayer()).use(solanaRpc({rpcUrl: "http://localhost:8899",rpcSubscriptionsUrl: "ws://localhost:8900"})).use(rpcAirdrop()).use(airdropPayer(lamports(1_000_000_000n)));const mint = await generateKeyPairSigner();const burnAuthority = await generateKeyPairSigner();const permissionedBurnExtension = extension("PermissionedBurn", {authority: burnAuthority.address});const mintSpace = BigInt(getMintSize([permissionedBurnExtension]));const mintRent = await client.rpc.getMinimumBalanceForRentExemption(mintSpace).send();await client.sendTransaction([getCreateAccountInstruction({payer: client.payer,newAccount: mint,lamports: mintRent,space: mintSpace,programAddress: TOKEN_2022_PROGRAM_ADDRESS})]);
Inizializza PermissionedBurn
Inizializza l'estensione PermissionedBurnConfig sul mint con l'autorità di
burn.
const client = await createClient().use(generatedPayer()).use(solanaRpc({rpcUrl: "http://localhost:8899",rpcSubscriptionsUrl: "ws://localhost:8900"})).use(rpcAirdrop()).use(airdropPayer(lamports(1_000_000_000n)));const mint = await generateKeyPairSigner();const burnAuthority = await generateKeyPairSigner();const permissionedBurnExtension = extension("PermissionedBurn", {authority: burnAuthority.address});const mintSpace = BigInt(getMintSize([permissionedBurnExtension]));const mintRent = await client.rpc.getMinimumBalanceForRentExemption(mintSpace).send();await client.sendTransaction([getCreateAccountInstruction({payer: client.payer,newAccount: mint,lamports: mintRent,space: mintSpace,programAddress: TOKEN_2022_PROGRAM_ADDRESS}),getInitializePermissionedBurnInstruction({mint: mint.address,authority: burnAuthority.address})]);
Inizializza il mint
Inizializza il mint con InitializeMint nella stessa transazione.
const client = await createClient().use(generatedPayer()).use(solanaRpc({rpcUrl: "http://localhost:8899",rpcSubscriptionsUrl: "ws://localhost:8900"})).use(rpcAirdrop()).use(airdropPayer(lamports(1_000_000_000n)));const mint = await generateKeyPairSigner();const burnAuthority = await generateKeyPairSigner();const permissionedBurnExtension = extension("PermissionedBurn", {authority: burnAuthority.address});const mintSpace = BigInt(getMintSize([permissionedBurnExtension]));const mintRent = await client.rpc.getMinimumBalanceForRentExemption(mintSpace).send();await client.sendTransaction([getCreateAccountInstruction({payer: client.payer,newAccount: mint,lamports: mintRent,space: mintSpace,programAddress: TOKEN_2022_PROGRAM_ADDRESS}),getInitializePermissionedBurnInstruction({mint: mint.address,authority: burnAuthority.address}),getInitializeMintInstruction({mint: mint.address,decimals: 0,mintAuthority: client.payer.address,freezeAuthority: client.payer.address})]);
Crea un token account e conia token
Crea un token account per il pagante e conia token su di esso.
const client = await createClient().use(generatedPayer()).use(solanaRpc({rpcUrl: "http://localhost:8899",rpcSubscriptionsUrl: "ws://localhost:8900"})).use(rpcAirdrop()).use(airdropPayer(lamports(1_000_000_000n)));const mint = await generateKeyPairSigner();const burnAuthority = await generateKeyPairSigner();const permissionedBurnExtension = extension("PermissionedBurn", {authority: burnAuthority.address});const mintSpace = BigInt(getMintSize([permissionedBurnExtension]));const mintRent = await client.rpc.getMinimumBalanceForRentExemption(mintSpace).send();await client.sendTransaction([getCreateAccountInstruction({payer: client.payer,newAccount: mint,lamports: mintRent,space: mintSpace,programAddress: TOKEN_2022_PROGRAM_ADDRESS}),getInitializePermissionedBurnInstruction({mint: mint.address,authority: burnAuthority.address}),getInitializeMintInstruction({mint: mint.address,decimals: 0,mintAuthority: client.payer.address,freezeAuthority: client.payer.address})]);const [sourceToken] = await findAssociatedTokenPda({mint: mint.address,owner: client.payer.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});await client.sendTransaction([await getCreateAssociatedTokenInstructionAsync({payer: client.payer,mint: mint.address,owner: client.payer.address}),getMintToCheckedInstruction({mint: mint.address,token: sourceToken,mintAuthority: client.payer,amount: 2n,decimals: 0})]);
Brucia con l'autorità di burn
Brucia i token con PermissionedBurnInstruction::BurnChecked firmato sia
dal proprietario del token account che dall'autorità di burn.
const client = await createClient().use(generatedPayer()).use(solanaRpc({rpcUrl: "http://localhost:8899",rpcSubscriptionsUrl: "ws://localhost:8900"})).use(rpcAirdrop()).use(airdropPayer(lamports(1_000_000_000n)));const mint = await generateKeyPairSigner();const burnAuthority = await generateKeyPairSigner();const permissionedBurnExtension = extension("PermissionedBurn", {authority: burnAuthority.address});const mintSpace = BigInt(getMintSize([permissionedBurnExtension]));const mintRent = await client.rpc.getMinimumBalanceForRentExemption(mintSpace).send();await client.sendTransaction([getCreateAccountInstruction({payer: client.payer,newAccount: mint,lamports: mintRent,space: mintSpace,programAddress: TOKEN_2022_PROGRAM_ADDRESS}),getInitializePermissionedBurnInstruction({mint: mint.address,authority: burnAuthority.address}),getInitializeMintInstruction({mint: mint.address,decimals: 0,mintAuthority: client.payer.address,freezeAuthority: client.payer.address})]);const [sourceToken] = await findAssociatedTokenPda({mint: mint.address,owner: client.payer.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});await client.sendTransaction([await getCreateAssociatedTokenInstructionAsync({payer: client.payer,mint: mint.address,owner: client.payer.address}),getMintToCheckedInstruction({mint: mint.address,token: sourceToken,mintAuthority: client.payer,amount: 2n,decimals: 0})]);await client.sendTransaction([getPermissionedBurnCheckedInstruction({account: sourceToken,mint: mint.address,permissionedBurnAuthority: burnAuthority,authority: client.payer,amount: 1n,decimals: 0})]);
Ordine delle istruzioni
PermissionedBurnInstruction::Initialize deve precedere
InitializeMint. CreateAccount,
PermissionedBurnInstruction::Initialize e InitializeMint devono
essere inclusi nella stessa transazione.
Riferimento alla sorgente
| Elemento | Descrizione | Sorgente |
|---|---|---|
PermissionedBurnConfig | Estensione del mint che memorizza l'autorità necessaria per co-firmare ogni burn per il mint. | Sorgente |
PermissionedBurnInstruction::Initialize | Istruzione che inizializza la configurazione del burn con permessi prima di InitializeMint. | Sorgente |
PermissionedBurnInstruction::Burn | Istruzione di burn che richiede le firme dell'autorità di burn e del proprietario o delegato del token account. | Sorgente |
PermissionedBurnInstruction::BurnChecked | Istruzione di burn con verifica dei decimali che utilizza gli stessi account di PermissionedBurnInstruction::Burn. | Sorgente |
AuthorityType::PermissionedBurn | Discriminatore di autorità utilizzato con SetAuthority per ruotare o disabilitare l'autorità di burn su un mint. | Sorgente |
process_initialize | Logica del processore che inizializza PermissionedBurnConfig su un mint non inizializzato e memorizza l'autorità di burn. | Sorgente |
process_burn | Logica del processore che rifiuta i burn standard quando l'autorità di burn è impostata e verifica la firma dell'autorità. | Sorgente |
process_set_authority | Logica del processore che valida e ruota l'autorità di burn quando viene utilizzato AuthorityType::PermissionedBurn. | Sorgente |
Typescript
L'esempio Kit di seguito utilizza direttamente le istruzioni generate. Il
pacchetto @solana/spl-token legacy pubblicato non include ancora il supporto
per il burn con permesso, quindi non è incluso alcun esempio legacy.
Kit
import {lamports,createClient,generateKeyPairSigner,unwrapOption} from "@solana/kit";import { solanaRpc, rpcAirdrop } from "@solana/kit-plugin-rpc";import { generatedPayer, airdropPayer } from "@solana/kit-plugin-signer";import { getCreateAccountInstruction } from "@solana-program/system";import {AuthorityType,extension,fetchMint,fetchToken,findAssociatedTokenPda,getBurnCheckedInstruction,getCreateAssociatedTokenInstructionAsync,getInitializeMintInstruction,getInitializePermissionedBurnInstruction,getMintSize,getMintToCheckedInstruction,getPermissionedBurnCheckedInstruction,getSetAuthorityInstruction,isExtension,TOKEN_2022_PROGRAM_ADDRESS} from "@solana-program/token-2022";const client = await createClient().use(generatedPayer()).use(solanaRpc({rpcUrl: "http://localhost:8899",rpcSubscriptionsUrl: "ws://localhost:8900"})).use(rpcAirdrop()).use(airdropPayer(lamports(1_000_000_000n)));const mint = await generateKeyPairSigner();const burnAuthority = await generateKeyPairSigner();const permissionedBurnExtension = extension("PermissionedBurn", {authority: burnAuthority.address});const mintSpace = BigInt(getMintSize([permissionedBurnExtension]));const mintRent = await client.rpc.getMinimumBalanceForRentExemption(mintSpace).send();await client.sendTransaction([getCreateAccountInstruction({payer: client.payer, // Account funding account creation.newAccount: mint, // New mint account to create.lamports: mintRent, // Lamports funding the mint account rent.space: mintSpace, // Account size in bytes for the mint plus PermissionedBurnConfig.programAddress: TOKEN_2022_PROGRAM_ADDRESS // Program that owns the mint account.}),getInitializePermissionedBurnInstruction({mint: mint.address, // Mint account that stores the PermissionedBurnConfig extension.authority: burnAuthority.address // Authority required to co-sign every burn for the mint.}),getInitializeMintInstruction({mint: mint.address, // Mint account to initialize.decimals: 0, // Number of decimals for the token.mintAuthority: client.payer.address, // Authority allowed to mint new tokens.freezeAuthority: client.payer.address // Authority allowed to freeze token accounts.})]);const [sourceToken] = await findAssociatedTokenPda({mint: mint.address,owner: client.payer.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});await client.sendTransaction([await getCreateAssociatedTokenInstructionAsync({payer: client.payer, // Account funding the associated token account creation.mint: mint.address, // Mint for the associated token account.owner: client.payer.address // Owner of the associated token account.}),getMintToCheckedInstruction({mint: mint.address, // Mint account that issues the tokens.token: sourceToken, // Token account receiving the newly minted tokens.mintAuthority: client.payer, // Signer authorized to mint new tokens.amount: 2n, // Token amount in base units.decimals: 0 // Decimals defined on the mint.})]);let standardBurnFailure: string | undefined;try {await client.sendTransaction([getBurnCheckedInstruction({account: sourceToken, // Token account to burn from.mint: mint.address, // Mint with the permissioned burn configuration.authority: client.payer, // Token account owner signing the burn.amount: 1n, // Token amount in base units.decimals: 0 // Decimals defined on the mint.})]);} catch (error) {standardBurnFailure = error instanceof Error ? error.message : String(error);}if (!standardBurnFailure) {throw new Error("Expected the standard burn to fail");}await client.sendTransaction([getPermissionedBurnCheckedInstruction({account: sourceToken, // Token account to burn from.mint: mint.address, // Mint with the permissioned burn configuration.permissionedBurnAuthority: burnAuthority, // Burn authority co-signing the burn.authority: client.payer, // Token account owner signing the burn.amount: 1n, // Token amount in base units.decimals: 0 // Decimals defined on the mint.})]);await client.sendTransaction([getSetAuthorityInstruction({owned: mint.address, // Mint with the permissioned burn configuration.owner: burnAuthority, // Current burn authority signing the update.authorityType: AuthorityType.PermissionedBurn, // Authority type to update.newAuthority: null // Setting the authority to None disables permissioned burning.})]);await client.sendTransaction([getBurnCheckedInstruction({account: sourceToken, // Token account to burn from.mint: mint.address, // Mint with permissioned burning disabled.authority: client.payer, // Token account owner signing the burn.amount: 1n, // Token amount in base units.decimals: 0 // Decimals defined on the mint.})]);const sourceAccount = await fetchToken(client.rpc, sourceToken);const mintAccount = await fetchMint(client.rpc, mint.address);const permissionedBurnConfig = (unwrapOption(mintAccount.data.extensions) ?? []).find((item) => isExtension("PermissionedBurn", item));console.log("Mint Address:", mint.address);console.log("Error From Failed Standard Burn:", standardBurnFailure);console.log("Source Amount After Burns:", sourceAccount.data.amount);console.log("Extension After Disable:", permissionedBurnConfig);
Rust
use anyhow::{anyhow, Result};use solana_client::nonblocking::rpc_client::RpcClient;use solana_commitment_config::CommitmentConfig;use solana_sdk::{signature::{Keypair, Signer},transaction::Transaction,};use solana_system_interface::instruction::create_account;use spl_associated_token_account_interface::{address::get_associated_token_address_with_program_id,instruction::create_associated_token_account,};use spl_token_2022_interface::{extension::{permissioned_burn::{instruction as permissioned_burn_ix, PermissionedBurnConfig},BaseStateWithExtensions, ExtensionType, StateWithExtensions,},instruction::{burn_checked, initialize_mint, mint_to_checked, set_authority, AuthorityType},state::{Account, Mint},ID as TOKEN_2022_PROGRAM_ID,};#[tokio::main]async fn main() -> Result<()> {let client = RpcClient::new_with_commitment(String::from("http://localhost:8899"),CommitmentConfig::confirmed(),);let fee_payer = Keypair::new();let burn_authority = Keypair::new();let airdrop_signature = client.request_airdrop(&fee_payer.pubkey(), 5_000_000_000).await?;loop {let confirmed = client.confirm_transaction(&airdrop_signature).await?;if confirmed {break;}}let mint = Keypair::new();let mint_space =ExtensionType::try_calculate_account_len::<Mint>(&[ExtensionType::PermissionedBurn])?;let mint_rent = client.get_minimum_balance_for_rent_exemption(mint_space).await?;let create_mint_transaction = Transaction::new_signed_with_payer(&[create_account(&fee_payer.pubkey(), // Account funding account creation.&mint.pubkey(), // New mint account to create.mint_rent, // Lamports funding the mint account rent.mint_space as u64, // Account size in bytes for the mint plus PermissionedBurnConfig.&TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.),permissioned_burn_ix::initialize(&TOKEN_2022_PROGRAM_ID, // Token program that owns the mint.&mint.pubkey(), // Mint account that stores the PermissionedBurnConfig extension.&burn_authority.pubkey(), // Authority required to co-sign every burn for the mint.)?,initialize_mint(&TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.&mint.pubkey(), // Mint account to initialize.&fee_payer.pubkey(), // Authority allowed to mint new tokens.Some(&fee_payer.pubkey()), // Authority allowed to freeze token accounts.0, // Number of decimals for the token.)?,],Some(&fee_payer.pubkey()),&[&fee_payer, &mint],client.get_latest_blockhash().await?,);client.send_and_confirm_transaction(&create_mint_transaction).await?;let source_token = get_associated_token_address_with_program_id(&fee_payer.pubkey(),&mint.pubkey(),&TOKEN_2022_PROGRAM_ID,);let create_token_account_transaction = Transaction::new_signed_with_payer(&[create_associated_token_account(&fee_payer.pubkey(), // Account funding the associated token account creation.&fee_payer.pubkey(), // Owner of the associated token account.&mint.pubkey(), // Mint for the associated token account.&TOKEN_2022_PROGRAM_ID, // Token program that owns the token account.),mint_to_checked(&TOKEN_2022_PROGRAM_ID, // Token program that owns the mint and token account.&mint.pubkey(), // Mint account that issues the tokens.&source_token, // Token account receiving the newly minted tokens.&fee_payer.pubkey(), // Signer authorized to mint new tokens.&[&fee_payer.pubkey()], // Additional multisig signers.2, // Token amount in base units.0, // Decimals defined on the mint.)?,],Some(&fee_payer.pubkey()),&[&fee_payer],client.get_latest_blockhash().await?,);client.send_and_confirm_transaction(&create_token_account_transaction).await?;let standard_burn_ix = burn_checked(&TOKEN_2022_PROGRAM_ID, // Token program that processes the burn.&source_token, // Token account to burn from.&mint.pubkey(), // Mint with the permissioned burn configuration.&fee_payer.pubkey(), // Token account owner signing the burn.&[&fee_payer.pubkey()], // Additional multisig signers.1, // Token amount in base units.0, // Decimals defined on the mint.)?;let standard_burn_transaction = Transaction::new_signed_with_payer(&[standard_burn_ix],Some(&fee_payer.pubkey()),&[&fee_payer],client.get_latest_blockhash().await?,);let standard_burn_result = client.simulate_transaction(&standard_burn_transaction).await?;let standard_burn_failure = standard_burn_result.value.err.ok_or_else(|| anyhow!("Expected the standard burn to fail"))?;let permissioned_burn_instruction = permissioned_burn_ix::burn_checked(&TOKEN_2022_PROGRAM_ID, // Token program that processes the burn.&source_token, // Token account to burn from.&mint.pubkey(), // Mint with the permissioned burn configuration.&burn_authority.pubkey(), // Burn authority co-signing the burn.&fee_payer.pubkey(), // Token account owner signing the burn.&[], // Additional multisig signers.1, // Token amount in base units.0, // Decimals defined on the mint.)?;let permissioned_burn_transaction = Transaction::new_signed_with_payer(&[permissioned_burn_instruction],Some(&fee_payer.pubkey()),&[&fee_payer, &burn_authority],client.get_latest_blockhash().await?,);client.send_and_confirm_transaction(&permissioned_burn_transaction).await?;let disable_authority_transaction = Transaction::new_signed_with_payer(&[set_authority(&TOKEN_2022_PROGRAM_ID, // Token program that owns the mint.&mint.pubkey(), // Mint with the permissioned burn configuration.None, // Setting the authority to None disables permissioned burning.AuthorityType::PermissionedBurn, // Authority type to update.&burn_authority.pubkey(), // Current burn authority signing the update.&[], // Additional multisig signers.)?,],Some(&fee_payer.pubkey()),&[&fee_payer, &burn_authority],client.get_latest_blockhash().await?,);client.send_and_confirm_transaction(&disable_authority_transaction).await?;let standard_burn_after_disable_transaction = Transaction::new_signed_with_payer(&[burn_checked(&TOKEN_2022_PROGRAM_ID, // Token program that processes the burn.&source_token, // Token account to burn from.&mint.pubkey(), // Mint with permissioned burning disabled.&fee_payer.pubkey(), // Token account owner signing the burn.&[&fee_payer.pubkey()], // Additional multisig signers.1, // Token amount in base units.0, // Decimals defined on the mint.)?,],Some(&fee_payer.pubkey()),&[&fee_payer],client.get_latest_blockhash().await?,);client.send_and_confirm_transaction(&standard_burn_after_disable_transaction).await?;let source_account = client.get_account(&source_token).await?;let source_state = StateWithExtensions::<Account>::unpack(&source_account.data)?;let mint_account = client.get_account(&mint.pubkey()).await?;let mint_state = StateWithExtensions::<Mint>::unpack(&mint_account.data)?;let permissioned_burn_config = mint_state.get_extension::<PermissionedBurnConfig>()?;println!("Mint Address: {}", mint.pubkey());println!("Error From Failed Standard Burn: {:?}",standard_burn_failure);println!("Source Amount After Burns: {}",u64::from(source_state.base.amount));println!("Extension After Disable: {:?}", permissioned_burn_config);Ok(())}
Is this page helpful?