Tokeny nieprzekazywalne

Czym są tokeny nieprzekazywalne?

Rozszerzenie NonTransferable mint Token Extensions Program sprawia, że każde konto tokenów dla tego mint staje się nieprzekazywalne. Po zmintowaniu tokenów posiadacze tokenów nie mogą przenieść ich na inne konto tokenów za pomocą Transfer lub TransferChecked.

Ten wzorzec jest przydatny w przypadku aktywów, które powinny pozostać powiązane z jednym portfelem.

Tokeny nieprzekazywalne nadal mogą być:

  • Mintowane przez uprawnienie mint
  • Spalane przez właściciela konta tokenów lub upoważnionego delegata
  • Konta tokenów mogą być zamykane po osiągnięciu salda zero

Rozszerzenia konta tokenów

Gdy konto tokenów jest inicjalizowane dla mint z NonTransferable, konto tokenów jest inicjalizowane z NonTransferableAccount i ImmutableOwner. Gdy konto tokenów jest tworzone przez Associated Token Program, obliczany jest wymagany rozmiar konta, a konto tokenów jest tworzone z wymaganym rozmiarem i lamport zwolnionymi z rent.

Jak utworzyć mint nieprzekazywalne

Aby utworzyć mint nieprzekazywalne:

  1. Oblicz rozmiar mint account i rent potrzebne dla mint oraz rozszerzenia NonTransferable.
  2. Utwórz mint account za pomocą CreateAccount, zainicjalizuj NonTransferable i zainicjalizuj mint za pomocą InitializeMint.
  3. Utwórz konta tokenów dla mint. NonTransferableAccount i ImmutableOwner są automatycznie włączone dla kont tokenów.
  4. Transfer i TransferChecked kończą się niepowodzeniem z TokenError::NonTransferable.

Oblicz rozmiar konta

Oblicz rozmiar mint account dla podstawowego mint plus rozszerzenie NonTransferable. To jest rozmiar używany w CreateAccount.

Oblicz czynsz

Oblicz czynsz używając rozmiaru potrzebnego dla mint oraz rozszerzenia NonTransferable.

Utwórz mint account

Utwórz mint account z obliczoną przestrzenią i lamportami.

Zainicjalizuj NonTransferable

Zainicjalizuj rozszerzenie NonTransferable na mint.

Zainicjalizuj mint

Zainicjalizuj mint za pomocą InitializeMint w tej samej transakcji.

Oblicz rozmiar konta

Oblicz rozmiar mint account dla podstawowego mint plus rozszerzenie NonTransferable. To jest rozmiar używany w CreateAccount.

Oblicz czynsz

Oblicz czynsz używając rozmiaru potrzebnego dla mint oraz rozszerzenia NonTransferable.

Utwórz mint account

Utwórz mint account z obliczoną przestrzenią i lamportami.

Zainicjalizuj NonTransferable

Zainicjalizuj rozszerzenie NonTransferable na mint.

Zainicjalizuj mint

Zainicjalizuj mint za pomocą InitializeMint w tej samej transakcji.

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

Kolejność Instrukcji

InitializeNonTransferableMint musi być przed InitializeMint. CreateAccount, InitializeNonTransferableMint oraz InitializeMint muszą być zawarte w tej samej transakcji.

Źródło

ElementOpisŹródło
NonTransferableRozszerzenie mint, które oznacza tokeny z mint jako nieprzekazywalne.Źródło
NonTransferableAccountRozszerzenie token account dodane do token accounts dla nieprzekazywalnych mint.Źródło
ImmutableOwnerRozszerzenie token account, które zapobiega zmianom własności i jest wymagane dla nieprzekazywalnych token accounts.Źródło
InitializeNonTransferableMintInstrukcja inicjalizująca rozszerzenie nieprzekazywalności na poziomie mint przed InitializeMint.Źródło
process_initialize_non_transferable_mintLogika procesora inicjalizująca rozszerzenie mint NonTransferable na niezainicjalizowanym mint.Źródło
get_required_init_account_extensionsSłuży do automatycznego dodawania rozszerzeń token account podczas inicjalizacji token accounts na podstawie rozszerzeń włączonych na mint. Dla mint NonTransferable dodaje NonTransferableAccount oraz ImmutableOwner.Źródło

Typescript

Poniższy przykład Kit używa bezpośrednio wygenerowanych instrukcji. Starsze przykłady używające @solana/web3.js są dołączone jako odniesienie.

Zestaw

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?

Spis treści

Edytuj stronę

Zarządzane przez

© 2026 Solana Foundation.
Wszelkie prawa zastrzeżone.
Bądź na bieżąco