ما هي الرموز غير القابلة للتحويل؟
إن امتداد mint الخاص بـ NonTransferable في Token Extensions Program يجعل
كل token account لذلك الـ mint غير قابل للتحويل. بعد سك الرموز، لا يمكن لحاملي
الرموز نقلها إلى token account آخر باستخدام Transfer أو
TransferChecked.
هذا النمط مفيد للأصول التي يجب أن تبقى مرتبطة بمحفظة واحدة.
لا تزال الرموز غير القابلة للتحويل قابلة لـ:
- السك بواسطة صلاحية الـ mint
- الحرق بواسطة مالك الـ token account أو مفوض معتمد
- يمكن إغلاق حسابات الرموز بعد وصول الرصيد إلى الصفر
امتدادات حساب الرمز
عندما يتم تهيئة token account لـ mint مع NonTransferable، يتم تهيئة الـ
token account مع NonTransferableAccount و ImmutableOwner. عندما
يتم إنشاء الـ token account من خلال Associated Token
Program،
يتم حساب حجم الحساب المطلوب وإنشاء الـ token account بالحجم المطلوب والـ
lamports المعفاة من الـ rent.
كيفية إنشاء Mint غير قابل للتحويل
لإنشاء mint غير قابل للتحويل:
- احسب حجم حساب الـ mint والـ rent المطلوب للـ mint وامتداد
NonTransferable. - أنشئ حساب الـ mint باستخدام
CreateAccount، وهيئNonTransferable، وهيئ الـ mint باستخدامInitializeMint. - أنشئ حسابات رموز للـ mint. يتم تمكين
NonTransferableAccountوImmutableOwnerتلقائيًا لحسابات الرموز. - تفشل
TransferوTransferCheckedمعTokenError::NonTransferable.
احسب حجم الحساب
احسب حجم حساب الـ mint للـ mint الأساسي بالإضافة إلى امتداد
NonTransferable. هذا هو الحجم المستخدم في CreateAccount.
حساب الإيجار
احسب الإيجار باستخدام المساحة المطلوبة لعملية السك بالإضافة إلى إضافة
NonTransferable.
إنشاء حساب السك
أنشئ حساب السك بالمساحة واللامبورت المحسوبة.
تهيئة NonTransferable
قم بتهيئة إضافة NonTransferable على حساب السك.
تهيئة حساب السك
قم بتهيئة حساب السك مع InitializeMint في نفس المعاملة.
ترتيب التعليمات
يجب أن تأتي InitializeNonTransferableMint قبل InitializeMint. يجب
تضمين CreateAccount و InitializeNonTransferableMint و
InitializeMint في نفس المعاملة.
المرجع المصدري
| العنصر | الوصف | المصدر |
|---|---|---|
NonTransferable | إضافة حساب السك التي تحدد الرموز من السك على أنها غير قابلة للنقل. | المصدر |
NonTransferableAccount | إضافة حساب الرمز المضافة إلى حسابات الرموز لعمليات السك غير القابلة للنقل. | المصدر |
ImmutableOwner | إضافة حساب الرمز التي تمنع تغييرات الملكية ومطلوبة لحسابات الرموز غير القابلة للنقل. | المصدر |
InitializeNonTransferableMint | التعليمات التي تهيئ إضافة غير القابلة للنقل على مستوى السك قبل InitializeMint. | المصدر |
process_initialize_non_transferable_mint | منطق المعالج الذي يهيئ إضافة حساب السك NonTransferable على حساب سك غير مهيأ. | المصدر |
get_required_init_account_extensions | يُستخدم لإضافة إضافات حساب الرمز تلقائياً عند تهيئة حسابات الرموز بناءً على الإضافات الممكّنة على حساب السك. بالنسبة لعمليات السك NonTransferable، يضيف NonTransferableAccount و ImmutableOwner. | المصدر |
تايب سكريبت
يستخدم مثال Kit أدناه التعليمات المُولدة مباشرة. تم تضمين أمثلة قديمة تستخدم
@solana/web3.js للإشارة.
المجموعة
import { lamports, createClient, generateKeyPairSigner } 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 {extension,fetchMint,fetchToken,findAssociatedTokenPda,getCreateAssociatedTokenInstructionAsync,getInitializeMintInstruction,getInitializeNonTransferableMintInstruction,getMintSize,getMintToCheckedInstruction,getTransferCheckedInstruction,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 recipient = await generateKeyPairSigner();const nonTransferableExtension = extension("NonTransferable", {});const mintSpace = BigInt(getMintSize([nonTransferableExtension]));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}),getInitializeNonTransferableMintInstruction({mint: mint.address // Mint account that stores the NonTransferable extension.}),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});const [destinationToken] = await findAssociatedTokenPda({mint: mint.address,owner: recipient.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});await client.sendTransaction([await getCreateAssociatedTokenInstructionAsync({payer: client.payer,mint: mint.address,owner: client.payer.address}),await getCreateAssociatedTokenInstructionAsync({payer: client.payer,mint: mint.address,owner: recipient.address}),getMintToCheckedInstruction({mint: mint.address,token: sourceToken,mintAuthority: client.payer,amount: 1n,decimals: 0})]);try {await client.sendTransaction([getTransferCheckedInstruction({source: sourceToken, // Token account sending the transfer.mint: mint.address, // Mint with the non-transferable configuration.destination: destinationToken, // Token account receiving the transfer.authority: client.payer, // Signer approving the transfer.amount: 1n, // Token amount in base units.decimals: 0 // Decimals defined on the mint.})]);} catch (error) {console.error("Transfer failed as expected:", error);}const mintAccount = await fetchMint(client.rpc, mint.address);const sourceTokenAccount = await fetchToken(client.rpc, sourceToken);console.log("Mint Address:", mint.address);console.log("Mint Extensions:", mintAccount.data.extensions);console.log("\nSource ATA:", sourceToken);console.log("Source Token Extensions:", sourceTokenAccount.data.extensions);console.log("Destination ATA:", destinationToken);
Web3.js
import {Connection,Keypair,LAMPORTS_PER_SOL,sendAndConfirmTransaction,SystemProgram,Transaction} from "@solana/web3.js";import {ASSOCIATED_TOKEN_PROGRAM_ID,createAssociatedTokenAccountInstruction,createInitializeMintInstruction,createInitializeNonTransferableMintInstruction,createMintToCheckedInstruction,createTransferCheckedInstruction,ExtensionType,getAccount,getAssociatedTokenAddressSync,getMint,getMintLen,getNonTransferable,TOKEN_2022_PROGRAM_ID} from "@solana/spl-token";const connection = new Connection("http://localhost:8899", "confirmed");const latestBlockhash = await connection.getLatestBlockhash();const feePayer = Keypair.generate();const recipient = Keypair.generate();const airdropSignature = await connection.requestAirdrop(feePayer.publicKey,LAMPORTS_PER_SOL);await connection.confirmTransaction({blockhash: latestBlockhash.blockhash,lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,signature: airdropSignature});const mint = Keypair.generate();const mintSpace = getMintLen([ExtensionType.NonTransferable]);const mintRent = await connection.getMinimumBalanceForRentExemption(mintSpace);const sourceToken = getAssociatedTokenAddressSync(mint.publicKey,feePayer.publicKey,false,TOKEN_2022_PROGRAM_ID,ASSOCIATED_TOKEN_PROGRAM_ID);const destinationToken = getAssociatedTokenAddressSync(mint.publicKey,recipient.publicKey,false,TOKEN_2022_PROGRAM_ID,ASSOCIATED_TOKEN_PROGRAM_ID);await sendAndConfirmTransaction(connection,new Transaction({feePayer: feePayer.publicKey,blockhash: latestBlockhash.blockhash,lastValidBlockHeight: latestBlockhash.lastValidBlockHeight}).add(SystemProgram.createAccount({fromPubkey: feePayer.publicKey,newAccountPubkey: mint.publicKey,lamports: mintRent,space: mintSpace,programId: TOKEN_2022_PROGRAM_ID}),createInitializeNonTransferableMintInstruction(mint.publicKey, // Mint account that stores the NonTransferable extension.TOKEN_2022_PROGRAM_ID // Token program that owns the mint.),createInitializeMintInstruction(mint.publicKey,0,feePayer.publicKey,feePayer.publicKey,TOKEN_2022_PROGRAM_ID)),[feePayer, mint],{ commitment: "confirmed" });await sendAndConfirmTransaction(connection,new Transaction().add(createAssociatedTokenAccountInstruction(feePayer.publicKey,sourceToken,feePayer.publicKey,mint.publicKey,TOKEN_2022_PROGRAM_ID,ASSOCIATED_TOKEN_PROGRAM_ID),createAssociatedTokenAccountInstruction(feePayer.publicKey,destinationToken,recipient.publicKey,mint.publicKey,TOKEN_2022_PROGRAM_ID,ASSOCIATED_TOKEN_PROGRAM_ID),createMintToCheckedInstruction(mint.publicKey,sourceToken,feePayer.publicKey,1,0,[],TOKEN_2022_PROGRAM_ID)),[feePayer],{ commitment: "confirmed" });try {await sendAndConfirmTransaction(connection,new Transaction().add(createTransferCheckedInstruction(sourceToken, // Token account sending the transfer.mint.publicKey, // Mint with the non-transferable configuration.destinationToken, // Token account receiving the transfer.feePayer.publicKey, // Signer approving the transfer.1, // Token amount in base units.0, // Decimals defined on the mint.[], // Additional multisig signers.TOKEN_2022_PROGRAM_ID // Token program that processes the transfer.)),[feePayer],{ commitment: "confirmed" });} catch (error) {console.error("Transfer failed as expected:", error);}const mintAccount = await getMint(connection,mint.publicKey,"confirmed",TOKEN_2022_PROGRAM_ID);const sourceTokenAccount = await getAccount(connection,sourceToken,"confirmed",TOKEN_2022_PROGRAM_ID);console.log("Mint Address:", mint.publicKey.toBase58());console.log("Has NonTransferable:", getNonTransferable(mintAccount) !== null);console.log("\nSource ATA:", sourceToken.toBase58());console.log("Source Token Account:", sourceTokenAccount);console.log("Destination ATA:", destinationToken.toBase58());
Rust
use 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::{non_transferable::{NonTransferable, NonTransferableAccount},BaseStateWithExtensions, ExtensionType, StateWithExtensions,},instruction::{initialize_mint, initialize_non_transferable_mint, mint_to_checked, transfer_checked,},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 recipient = Keypair::new();let fee_payer = Keypair::new();let decimals = 0;let airdrop_signature = client.request_airdrop(&fee_payer.pubkey(), 1_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::NonTransferable])?;let mint_rent = client.get_minimum_balance_for_rent_exemption(mint_space).await?;let mint_blockhash = client.get_latest_blockhash().await?;let mint_transaction = Transaction::new_signed_with_payer(&[create_account(&fee_payer.pubkey(),&mint.pubkey(),mint_rent,mint_space as u64,&TOKEN_2022_PROGRAM_ID,),initialize_non_transferable_mint(&TOKEN_2022_PROGRAM_ID, &mint.pubkey())?,initialize_mint(&TOKEN_2022_PROGRAM_ID,&mint.pubkey(),&fee_payer.pubkey(),Some(&fee_payer.pubkey()),decimals,)?,],Some(&fee_payer.pubkey()),&[&fee_payer, &mint],mint_blockhash,);client.send_and_confirm_transaction(&mint_transaction).await?;let source_token_address = get_associated_token_address_with_program_id(&fee_payer.pubkey(),&mint.pubkey(),&TOKEN_2022_PROGRAM_ID,);let destination_token_address = get_associated_token_address_with_program_id(&recipient.pubkey(),&mint.pubkey(),&TOKEN_2022_PROGRAM_ID,);let setup_blockhash = client.get_latest_blockhash().await?;let setup_transaction = Transaction::new_signed_with_payer(&[create_associated_token_account(&fee_payer.pubkey(),&fee_payer.pubkey(),&mint.pubkey(),&TOKEN_2022_PROGRAM_ID,),create_associated_token_account(&fee_payer.pubkey(),&recipient.pubkey(),&mint.pubkey(),&TOKEN_2022_PROGRAM_ID,),mint_to_checked(&TOKEN_2022_PROGRAM_ID,&mint.pubkey(),&source_token_address,&fee_payer.pubkey(),&[],1,decimals,)?,],Some(&fee_payer.pubkey()),&[&fee_payer],setup_blockhash,);client.send_and_confirm_transaction(&setup_transaction).await?;let transfer_blockhash = client.get_latest_blockhash().await?;let transfer_transaction = Transaction::new_signed_with_payer(&[transfer_checked(&TOKEN_2022_PROGRAM_ID, // Token program to invoke.&source_token_address, // Token account sending the tokens.&mint.pubkey(), // Mint for the token being transferred.&destination_token_address, // Token account receiving the tokens.&fee_payer.pubkey(), // Owner or delegate approving the transfer.&[], // Additional multisig signers.1, // Token amount in base units.decimals, // Decimals defined on the mint account.)?,],Some(&fee_payer.pubkey()),&[&fee_payer],transfer_blockhash,);match client.send_and_confirm_transaction(&transfer_transaction).await {Ok(signature) => println!("Transfer unexpectedly succeeded: {}", signature),Err(error) => println!("Transfer failed as expected: {error:#?}"),}let mint_account = client.get_account(&mint.pubkey()).await?;let mint_state = StateWithExtensions::<Mint>::unpack(&mint_account.data)?;let source_token_account = client.get_account(&source_token_address).await?;let source_token_state = StateWithExtensions::<Account>::unpack(&source_token_account.data)?;println!("Mint Address: {}", mint.pubkey());println!("Mint Extensions: {:?}", mint_state.get_extension_types()?);println!("Has NonTransferable: {}",mint_state.get_extension::<NonTransferable>().is_ok());println!("\nSource ATA: {}", source_token_address);println!("Source Token Extensions: {:?}",source_token_state.get_extension_types()?);println!("Has NonTransferableAccount: {}",source_token_state.get_extension::<NonTransferableAccount>().is_ok());println!("Destination ATA: {}", destination_token_address);Ok(())}
Is this page helpful?