Siirtokelvottomat tokenit

Mitä ovat siirtokelvottomat tokenit?

Token Extensions Programin NonTransferable mint-laajennus tekee jokaisesta kyseisen mintin token accountista siirtokelvottoman. Tokenien minttauksen jälkeen token-haltijat eivät voi siirtää niitä toiseen token accountiin Transfer- tai TransferChecked-komennoilla.

Tämä malli on hyödyllinen omaisuuserille, joiden tulisi pysyä kiinnitettynä yhteen lompakkoon.

Siirtokelvottomia tokeneita voidaan edelleen:

  • Minttaa mint-valtuutuksen toimesta
  • Polttaa token accountin omistajan tai valtuutetun delegaatin toimesta
  • Token accountit voidaan sulkea sen jälkeen, kun saldo saavuttaa nollan

Token Account -laajennukset

Kun token account alustetaan mintille, jossa on NonTransferable, token account alustetaan NonTransferableAccount- ja ImmutableOwner-laajennuksilla. Kun token account luodaan Associated Token Programin kautta, vaadittu tilin koko lasketaan ja token account luodaan vaaditun kokoisen ja rent-vapautuksen sisältävine lamportteineen.

Miten luoda siirtokelvoton mint

Siirtokelvottoman mintin luominen:

  1. Laske mint accountin koko ja tarvittava rent mintille ja NonTransferable-laajennukselle.
  2. Luo mint account CreateAccount-komennolla, alusta NonTransferable ja alusta mint InitializeMint-komennolla.
  3. Luo token accountit mintille. NonTransferableAccount ja ImmutableOwner otetaan automaattisesti käyttöön token accounteille.
  4. Transfer ja TransferChecked epäonnistuvat TokenError::NonTransferable-virheellä.

Laske tilin koko

Laske mint accountin koko perus-mintille sekä NonTransferable-laajennukselle. Tämä on koko, jota käytetään CreateAccount-komennossa.

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

Laske vuokra

Laske vuokra käyttäen mint-tiliä varten tarvittavaa tilaa plus NonTransferable -laajennus.

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]));
const mintRent = await client.rpc
.getMinimumBalanceForRentExemption(mintSpace)
.send();

Luo mint-tili

Luo mint-tili lasketulla tilalla ja lamporteilla.

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

Alusta NonTransferable

Alusta NonTransferable -laajennus mintille.

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

Alusta mint

Alusta mint käyttäen InitializeMint samassa transaktiossa.

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]));
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
}),
getInitializeMintInstruction({
mint: mint.address,
decimals: 0,
mintAuthority: client.payer.address,
freezeAuthority: client.payer.address
})
]);

Laske tilin koko

Laske mint accountin koko perus-mintille sekä NonTransferable-laajennukselle. Tämä on koko, jota käytetään CreateAccount-komennossa.

Laske vuokra

Laske vuokra käyttäen mint-tiliä varten tarvittavaa tilaa plus NonTransferable -laajennus.

Luo mint-tili

Luo mint-tili lasketulla tilalla ja lamporteilla.

Alusta NonTransferable

Alusta NonTransferable -laajennus mintille.

Alusta mint

Alusta mint käyttäen InitializeMint samassa transaktiossa.

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

Instruction Order

InitializeNonTransferableMint on tultava ennen komentoa InitializeMint. CreateAccount, InitializeNonTransferableMint ja InitializeMint on sisällytettävä samaan transaktioon.

Lähdeviittaus

KohdeKuvausLähde
NonTransferableMint-laajennus, joka merkitsee mintin tokenit ei-siirrettäviksi.Lähde
NonTransferableAccountToken account -laajennus, joka lisätään token-tileille ei-siirrettävissä minteissä.Lähde
ImmutableOwnerToken account -laajennus, joka estää omistajuuden muutokset ja vaaditaan ei-siirrettäville token-tileille.Lähde
InitializeNonTransferableMintKomento, joka alustaa mint-tason ei-siirrettävä-laajennuksen ennen komentoa InitializeMint.Lähde
process_initialize_non_transferable_mintProsessorilogiikka, joka alustaa NonTransferable mint-laajennuksen alustamattomalle mintille.Lähde
get_required_init_account_extensionsKäytetään lisäämään automaattisesti token account -laajennuksia, kun token-tilejä alustetaan mintissä käytössä olevien laajennusten perusteella. NonTransferable -minteille lisätään NonTransferableAccount ja ImmutableOwner.Lähde

Typescript

Alla oleva Kit -esimerkki käyttää generoituja komentoja suoraan. Vanhat esimerkit käyttäen @solana/web3.js ovat mukana viitteenä.

Kit

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?

Sisällysluettelo

Muokkaa sivua

Hallinnoi

© 2026 Solana Foundation.
Kaikki oikeudet pidätetään.
Yhdistä