Czym Jest Kontrolowane Spalanie?
Rozszerzenie mint PermissionedBurnConfig programu Token Extensions Program
wymaga, aby skonfigurowany organ spalania współpodpisywał każde spalenie dla
danego mintu.
Dopóki organ spalania jest ustawiony:
- Standardowe instrukcje
BurniBurnCheckedkończą się błędemTokenError::InvalidInstruction. - Spalanie musi odbywać się za pomocą
PermissionedBurnInstruction::BurnlubPermissionedBurnInstruction::BurnChecked, podpisanych zarówno przez organ spalania, jak i właściciela token account lub jego delegata.
Organ spalania jest współsygnatariuszem, a nie zastępcą właściciela. Sam organ nie może spalać tokenów z cudzego token account, a właściciel token account nie może spalić tokenów bez podpisu organu. Stały delegat również musi korzystać z instrukcji kontrolowanego spalania i nadal potrzebuje współpodpisu organu spalania.
Pozwala to emitentowi synchronizować podaż tokenów z rejestrem off-chain — na przykład w przypadku tokenizowanego aktywa, które musi pozostawać w relacji 1:1 — poprzez uniemożliwienie posiadaczom jednostronnego spalania tokenów.
Organ spalania można później zmienić za pomocą SetAuthority używając
AuthorityType::PermissionedBurn. Ustawienie organu na None wyłącza
kontrolowane spalanie i ponownie włącza standardowe instrukcje spalania. Dane
rozszerzenia pozostają na mincie.
Dostępność
Kontrolowane spalanie jest dostępne w Token-2022
program@v11.0.0.
Jest dostępne na devnet już dziś i ma zostać uruchomione na mainnecie w
czerwcu 2026 r. Token Extensions Program jest wdrażany osobno dla każdego
klastra, dlatego upewnij się, że wdrożenie na docelowym klastrze obejmuje
wersję v11.0.0 lub nowszą.
Lokalny test validator zawiera również starszą kompilację Token-2022, więc aby uruchomić przykłady z tej strony, załaduj program v11.0.0 z devnet do swojego test validator:
solana program dump -u devnet TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb token_2022.sosolana-test-validator --reset --bpf-program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb token_2022.so
Jak utworzyć Mint i Burn z uprawnieniami
Aby utworzyć mint z uprawnionym spalaniem i spalić tokeny:
- Oblicz rozmiar mint account i rent potrzebny dla mint oraz rozszerzenia
PermissionedBurnConfig. - Utwórz mint account za pomocą
CreateAccount, zainicjalizujPermissionedBurnConfigi zainicjalizuj mint za pomocąInitializeMint. - Utwórz token account i wybij tokeny.
- Spal za pomocą
PermissionedBurnInstruction::BurnCheckedpodpisanego zarówno przez właściciela token account, jak i przez organ uprawniony do spalania. - Opcjonalnie wyłącz uprawione spalanie za pomocą
SetAuthorityużywającAuthorityType::PermissionedBurni autorytetuNone, co ponownie włącza standardowe instrukcje spalania. Pełne przykłady kodu poniżej pokazują ten krok.
Oblicz rozmiar konta
Oblicz rozmiar mint account dla bazowego mint plus rozszerzenie
PermissionedBurnConfig. Jest to rozmiar używany w 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]));
Oblicz rent
Oblicz rent używając rozmiaru potrzebnego dla mint plus rozszerzenie
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();
Utwórz mint account
Utwórz mint account z obliczoną przestrzenią i lamports.
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})]);
Zainicjalizuj PermissionedBurn
Zainicjalizuj rozszerzenie PermissionedBurnConfig na mint z organem
uprawnionym do spalania.
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})]);
Zainicjalizuj mint
Zainicjalizuj mint za pomocą InitializeMint w tej samej transakcji.
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})]);
Utwórz token account i wybij tokeny
Utwórz token account dla płatnika i wybij do niego tokeny.
Spalanie z uprawnieniem burn authority
Spalaj tokeny przy użyciu PermissionedBurnInstruction::BurnChecked
podpisanego zarówno przez właściciela token account, jak i burn authority.
Kolejność instrukcji
PermissionedBurnInstruction::Initialize musi poprzedzać
InitializeMint. CreateAccount,
PermissionedBurnInstruction::Initialize oraz InitializeMint muszą
być zawarte w tej samej transakcji.
Odniesienie do źródła
| Element | Opis | Źródło |
|---|---|---|
PermissionedBurnConfig | Rozszerzenie minta przechowujące uprawnienie wymagane do współpodpisania każdego spalenia dla minta. | Źródło |
PermissionedBurnInstruction::Initialize | Instrukcja inicjalizująca konfigurację permissioned burn przed InitializeMint. | Źródło |
PermissionedBurnInstruction::Burn | Instrukcja spalania wymagająca podpisów od burn authority oraz właściciela lub delegata token account. | Źródło |
PermissionedBurnInstruction::BurnChecked | Instrukcja spalania z weryfikacją liczby miejsc dziesiętnych, używająca tych samych kont co PermissionedBurnInstruction::Burn. | Źródło |
AuthorityType::PermissionedBurn | Dyskryminator uprawnienia używany z SetAuthority do rotacji lub wyłączenia burn authority na mincie. | Źródło |
process_initialize | Logika procesora inicjalizująca PermissionedBurnConfig na niezainicjalizowanym mincie i przechowująca burn authority. | Źródło |
process_burn | Logika procesora odrzucająca standardowe spalenia, gdy burn authority jest ustawione, oraz weryfikująca podpis tego uprawnienia. | Źródło |
process_set_authority | Logika procesora walidująca i rotująca burn authority podczas używania AuthorityType::PermissionedBurn. | Źródło |
TypeScript
Poniższy przykład z Kit używa bezpośrednio wygenerowanych instrukcji.
Opublikowany pakiet legacy @solana/spl-token nie zawiera jeszcze obsługi
uprawnionego spalania, dlatego nie dołączono przykładu 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?