Μη Μεταβιβάσιμα Tokens

Τι Είναι τα Μη Μεταβιβάσιμα Tokens;

Η επέκταση mint NonTransferable του Token Extensions Program καθιστά κάθε token account για αυτό το mint μη μεταβιβάσιμο. Αφού δημιουργηθούν τα tokens, οι κάτοχοι tokens δεν μπορούν να τα μεταφέρουν σε άλλο token account με Transfer ή TransferChecked.

Αυτό το μοτίβο είναι χρήσιμο για περιουσιακά στοιχεία που πρέπει να παραμένουν συνδεδεμένα με ένα πορτοφόλι.

Τα μη μεταβιβάσιμα tokens μπορούν ακόμα να:

  • Δημιουργηθούν (minted) από την αρχή του mint
  • Καούν (burned) από τον κάτοχο του token account ή έναν εξουσιοδοτημένο αντιπρόσωπο
  • Τα token accounts μπορούν να κλείσουν αφού το υπόλοιπο φτάσει το μηδέν

Επεκτάσεις Token Account

Όταν ένα token account αρχικοποιείται για ένα mint με NonTransferable, το token account αρχικοποιείται με NonTransferableAccount και ImmutableOwner. Όταν το token account δημιουργείται μέσω του Associated Token Program, υπολογίζεται το απαιτούμενο μέγεθος λογαριασμού και το token account δημιουργείται με το απαιτούμενο μέγεθος και τα lamports απαλλαγμένα από rent.

Πώς να Δημιουργήσετε ένα Μη Μεταβιβάσιμο Mint

Για να δημιουργήσετε ένα μη μεταβιβάσιμο mint:

  1. Υπολογίστε το μέγεθος του mint account και το rent που απαιτείται για το mint και την επέκταση NonTransferable.
  2. Δημιουργήστε το mint account με CreateAccount, αρχικοποιήστε το NonTransferable και αρχικοποιήστε το mint με InitializeMint.
  3. Δημιουργήστε token accounts για το mint. Τα NonTransferableAccount και ImmutableOwner ενεργοποιούνται αυτόματα για τα token accounts.
  4. Οι εντολές Transfer και TransferChecked αποτυγχάνουν με TokenError::NonTransferable.

Υπολογισμός μεγέθους λογαριασμού

Υπολογίστε το μέγεθος του mint account για το βασικό mint συν την επέκταση NonTransferable. Αυτό είναι το μέγεθος που χρησιμοποιείται στο CreateAccount.

Υπολογισμός ενοικίου

Υπολογίστε το ενοίκιο χρησιμοποιώντας το μέγεθος που απαιτείται για το mint συν την επέκταση NonTransferable.

Δημιουργία του mint account

Δημιουργήστε το mint account με τον υπολογισμένο χώρο και lamports.

Αρχικοποίηση NonTransferable

Αρχικοποιήστε την επέκταση NonTransferable στο mint.

Αρχικοποίηση του mint

Αρχικοποιήστε το mint με InitializeMint στην ίδια συναλλαγή.

Υπολογισμός μεγέθους λογαριασμού

Υπολογίστε το μέγεθος του mint account για το βασικό mint συν την επέκταση NonTransferable. Αυτό είναι το μέγεθος που χρησιμοποιείται στο CreateAccount.

Υπολογισμός ενοικίου

Υπολογίστε το ενοίκιο χρησιμοποιώντας το μέγεθος που απαιτείται για το mint συν την επέκταση NonTransferable.

Δημιουργία του mint account

Δημιουργήστε το mint account με τον υπολογισμένο χώρο και lamports.

Αρχικοποίηση NonTransferable

Αρχικοποιήστε την επέκταση NonTransferable στο mint.

Αρχικοποίηση του mint

Αρχικοποιήστε το mint με InitializeMint στην ίδια συναλλαγή.

Example
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]));

Σειρά Εντολών

Το InitializeNonTransferableMint πρέπει να προηγείται του InitializeMint. Τα CreateAccount, InitializeNonTransferableMint και InitializeMint πρέπει να συμπεριληφθούν στην ίδια συναλλαγή.

Αναφορά Πηγής

ΣτοιχείοΠεριγραφήΠηγή
NonTransferableΕπέκταση mint που σημειώνει τα tokens από το mint ως μη μεταβιβάσιμα.Πηγή
NonTransferableAccountΕπέκταση token account που προστίθεται σε token accounts για μη μεταβιβάσιμα mints.Πηγή
ImmutableOwnerΕπέκταση token account που αποτρέπει αλλαγές κυριότητας και απαιτείται για μη μεταβιβάσιμα token accounts.Πηγή
InitializeNonTransferableMintΕντολή που αρχικοποιεί την επέκταση μη μεταβιβασιμότητας σε επίπεδο mint πριν από το InitializeMint.Πηγή
process_initialize_non_transferable_mintΛογική επεξεργαστή που αρχικοποιεί την επέκταση mint NonTransferable σε ένα μη αρχικοποιημένο mint.Πηγή
get_required_init_account_extensionsΧρησιμοποιείται για την αυτόματη προσθήκη επεκτάσεων token account όταν τα token accounts αρχικοποιούνται με βάση τις επεκτάσεις που είναι ενεργοποιημένες στο mint. Για τα NonTransferable mints, προσθέτει NonTransferableAccount και ImmutableOwner.Πηγή

Typescript

Το παρακάτω παράδειγμα Kit χρησιμοποιεί απευθείας τις δημιουργημένες εντολές. Παραδείγματα παλαιού τύπου που χρησιμοποιούν @solana/web3.js περιλαμβάνονται για αναφορά.

Κιτ

Instructions
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);
Console
Click to execute the code.

Web3.js

Instructions
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());
Console
Click to execute the code.

Rust

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(())
}
Console
Click to execute the code.

Is this page helpful?

Πίνακας Περιεχομένων

Επεξεργασία Σελίδας

Διαχειρίζεται από

© 2026 Ίδρυμα Solana.
Με επιφύλαξη παντός δικαιώματος.
Συνδεθείτε