Apa Itu Permissioned Burn?
Ekstensi mint PermissionedBurnConfig dari Token Extension Program
mengharuskan burn authority yang dikonfigurasi untuk ikut menandatangani setiap
pembakaran pada mint tersebut.
Selama burn authority ditetapkan:
- Instruksi standar
BurndanBurnCheckedakan gagal denganTokenError::InvalidInstruction. - Pembakaran harus menggunakan
PermissionedBurnInstruction::BurnatauPermissionedBurnInstruction::BurnChecked, yang ditandatangani oleh burn authority dan pemilik atau delegasi token account.
Burn authority adalah co-penanda tangan, bukan pengganti pemilik. Authority saja tidak dapat membakar token dari token account milik orang lain, dan pemilik token account tidak dapat membakar tanpa tanda tangan authority. Permanent delegate pun harus menggunakan instruksi permissioned burn dan tetap memerlukan co-tanda tangan dari burn authority.
Hal ini memungkinkan penerbit menjaga sinkronisasi suplai token dengan catatan off-chain, misalnya aset yang ditokenisasi dan harus tetap didukung 1:1, dengan mencegah pemegang membakar token secara sepihak.
Burn authority dapat dirotasi di kemudian hari dengan SetAuthority
menggunakan AuthorityType::PermissionedBurn. Menetapkan authority ke
None akan menonaktifkan permissioned burning dan mengaktifkan kembali
instruksi burn standar. Data ekstensi itu sendiri tetap berada di mint.
Test validator lokal juga menyertakan build Token-2022 versi lama, jadi untuk menjalankan contoh-contoh di halaman ini, muat program v11.0.0 dari devnet ke dalam test validator Anda:
solana program dump -u devnet TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb token_2022.sosolana-test-validator --reset --bpf-program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb token_2022.so
Cara Membuat Mint dan Burn Dengan Izin
Untuk membuat mint dengan burn berizin dan membakar token:
- Hitung ukuran mint account dan rent yang dibutuhkan untuk mint dan ekstensi
PermissionedBurnConfig. - Buat mint account dengan
CreateAccount, inisialisasiPermissionedBurnConfig, dan inisialisasi mint denganInitializeMint. - Buat token account dan mint token.
- Burn dengan
PermissionedBurnInstruction::BurnCheckedyang ditandatangani oleh pemilik token account dan burn authority. - Opsional, nonaktifkan pembakaran berizin dengan
SetAuthoritymenggunakanAuthorityType::PermissionedBurndan authorityNone, yang mengaktifkan kembali instruksi burn standar. Contoh kode lengkap di bawah ini menunjukkan langkah ini.
Hitung ukuran akun
Hitung ukuran mint account untuk mint dasar ditambah ekstensi
PermissionedBurnConfig. Ini adalah ukuran yang digunakan dalam
CreateAccount.
Hitung rent
Hitung rent menggunakan ukuran yang dibutuhkan untuk mint ditambah ekstensi
PermissionedBurnConfig.
Buat mint account
Buat mint account dengan space dan lamport yang telah dihitung.
Inisialisasi PermissionedBurn
Inisialisasi ekstensi PermissionedBurnConfig pada mint dengan burn
authority.
Inisialisasi mint
Inisialisasi mint dengan InitializeMint dalam transaksi yang sama.
Buat token account dan mint token
Buat token account untuk payer dan mint token ke dalamnya.
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})]);
Burn dengan burn authority
Burn token dengan PermissionedBurnInstruction::BurnChecked yang
ditandatangani oleh pemilik token account dan burn authority.
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})]);
Urutan Instruksi
PermissionedBurnInstruction::Initialize harus didahulukan sebelum
InitializeMint. CreateAccount,
PermissionedBurnInstruction::Initialize, dan InitializeMint harus
disertakan dalam transaksi yang sama.
Referensi Sumber
| Item | Deskripsi | Sumber |
|---|---|---|
PermissionedBurnConfig | Ekstensi mint yang menyimpan otoritas yang diperlukan untuk menandatangani setiap burn bersama pada mint. | Sumber |
PermissionedBurnInstruction::Initialize | Instruksi yang menginisialisasi konfigurasi permissioned burn sebelum InitializeMint. | Sumber |
PermissionedBurnInstruction::Burn | Instruksi burn yang memerlukan tanda tangan dari burn authority dan pemilik atau delegasi token account. | Sumber |
PermissionedBurnInstruction::BurnChecked | Instruksi burn dengan pemeriksaan desimal yang menggunakan akun yang sama dengan PermissionedBurnInstruction::Burn. | Sumber |
AuthorityType::PermissionedBurn | Diskriminator otoritas yang digunakan bersama SetAuthority untuk merotasi atau menonaktifkan burn authority pada mint. | Sumber |
process_initialize | Logika pemroses yang menginisialisasi PermissionedBurnConfig pada mint yang belum diinisialisasi dan menyimpan burn authority. | Sumber |
process_burn | Logika pemroses yang menolak burn standar ketika burn authority disetel dan memverifikasi tanda tangan otoritas. | Sumber |
process_set_authority | Logika pemroses yang memvalidasi dan merotasi burn authority ketika AuthorityType::PermissionedBurn digunakan. | Sumber |
Typescript
Contoh Kit di bawah ini menggunakan instruksi yang dihasilkan secara langsung.
Paket @solana/spl-token warisan yang diterbitkan belum menyertakan dukungan
burn dengan izin, sehingga tidak ada contoh warisan yang disertakan.
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?