不変所有者

不変の所有者とは?

Token Extensions Programの*rsImmutableOwner*アカウント拡張機能により、トークンアカウントの所有者が永続的になります。

トークンアカウントがrsImmutableOwnerで初期化されると、後続のrsSetAuthority instructionsでその*rsAccountOwner*権限を変更できなくなります。

関連付けられたトークンアカウント

Token Extensions Program用のAssociated Token Programを通じて作成されたassociated token accountは、トークンアカウント上で_rsImmutableOwner_を自動的に初期化します。

不変の所有者アカウントの初期化方法

不変の所有者アカウントを初期化するには:

  1. ミントを作成して初期化します。
  2. *rsImmutableOwner*に十分なスペースを持つトークンアカウントを作成します。
  3. *rsInitializeAccountの前にトークンアカウント上でrsImmutableOwner*を初期化します。
  4. 後で*rsSetAuthority*でアカウント所有者を変更しようとすると、*rsTokenError::ImmutableOwner*で失敗します。

トークンアカウントサイズの計算

基本トークンアカウントと*rsImmutableOwner拡張機能のトークンアカウントサイズを計算します。これはrsCreateAccount*で使用されるサイズです。

rentの計算

*rsImmutableOwner*拡張機能に必要なトークンアカウントサイズを使用してrentを計算します。

トークンアカウントの作成

計算されたスペースとlamportでトークンアカウントを作成します。

ImmutableOwnerの初期化

token accountに*rsImmutableOwner*拡張を初期化します。

token accountの初期化

同じトランザクション内で*rsInitializeAccount*を使用してtoken accountを初期化します。

トークンアカウントサイズの計算

基本トークンアカウントと*rsImmutableOwner拡張機能のトークンアカウントサイズを計算します。これはrsCreateAccount*で使用されるサイズです。

rentの計算

*rsImmutableOwner*拡張機能に必要なトークンアカウントサイズを使用してrentを計算します。

トークンアカウントの作成

計算されたスペースとlamportでトークンアカウントを作成します。

ImmutableOwnerの初期化

token accountに*rsImmutableOwner*拡張を初期化します。

token accountの初期化

同じトランザクション内で*rsInitializeAccount*を使用してtoken accountを初期化します。

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 tokenAccount = await generateKeyPairSigner();
const mintSpace = BigInt(getMintSize());
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
}),
getInitializeMintInstruction({
mint: mint.address,
decimals: 0,
mintAuthority: client.payer.address,
freezeAuthority: client.payer.address
})
]);
const immutableOwnerExtension = extension("ImmutableOwner", {});
const tokenSpace = BigInt(getTokenSize([immutableOwnerExtension]));

instructionsの順序

_rsInitializeImmutableOwner_は_rsInitializeAccount_より前に実行する必要があります。 CreateAccountInitializeImmutableOwner、および_rsInitializeAccount_は同じトランザクションに含める必要があります。

ソースリファレンス

項目説明ソース
ImmutableOwnertoken account所有者を不変としてマークするアカウント拡張。ソース
InitializeImmutableOwner未初期化のtoken accountにimmutable-owner拡張を初期化するinstruction。ソース
process_initialize_immutable_ownerアカウントに*rsImmutableOwner*を割り当てて初期化するプロセッサロジック。ソース
SetAuthoritytoken account所有者を含む権限を変更するために使用される基本トークンinstruction。ソース
process_set_authoritytoken accountに*rsImmutableOwner*が有効な場合、所有者の変更を拒否するプロセッサロジック。ソース

Typescript

以下のKitの例では、生成されたinstructionsを直接使用しています。@solana/web3.jsおよび@solana/spl-tokenを使用したレガシー例も参考のために含まれています。

Kit

Instructions
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,
fetchToken,
getInitializeAccountInstruction,
getInitializeImmutableOwnerInstruction,
getInitializeMintInstruction,
getMintSize,
getSetAuthorityInstruction,
getTokenSize,
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 tokenAccount = await generateKeyPairSigner();
const newOwner = await generateKeyPairSigner();
const mintSpace = BigInt(getMintSize());
const mintRent = await client.rpc
.getMinimumBalanceForRentExemption(mintSpace)
.send();
await client.sendTransaction([
getCreateAccountInstruction({
payer: client.payer, // Account funding the new mint account.
newAccount: mint, // New mint account to create.
lamports: mintRent, // Lamports funding the mint account rent.
space: mintSpace, // Account size in bytes for the mint.
programAddress: TOKEN_2022_PROGRAM_ADDRESS // Program that owns the mint account.
}),
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 immutableOwnerExtension = extension("ImmutableOwner", {});
const tokenSpace = BigInt(getTokenSize([immutableOwnerExtension]));
const tokenRent = await client.rpc
.getMinimumBalanceForRentExemption(tokenSpace)
.send();
await client.sendTransaction([
getCreateAccountInstruction({
payer: client.payer, // Account funding the new token account.
newAccount: tokenAccount, // New token account to create.
lamports: tokenRent, // Lamports funding the token account rent.
space: tokenSpace, // Account size in bytes for the token account plus ImmutableOwner.
programAddress: TOKEN_2022_PROGRAM_ADDRESS // Program that owns the token account.
}),
getInitializeImmutableOwnerInstruction({
account: tokenAccount.address // Token account that stores the ImmutableOwner extension.
}),
getInitializeAccountInstruction({
account: tokenAccount.address, // Token account to initialize.
mint: mint.address, // Mint for the token account.
owner: client.payer.address // Owner of the token account.
})
]);
let failure: string | undefined;
try {
await client.sendTransaction([
getSetAuthorityInstruction({
owned: tokenAccount.address, // Token account whose authority is being updated.
owner: client.payer, // Current token account owner signing the instruction.
authorityType: AuthorityType.AccountOwner, // Account authority field to change.
newAuthority: newOwner.address // New token account owner to set.
})
]);
} catch (error) {
failure = String(error);
}
if (!failure) {
throw new Error("Expected the owner change to fail");
}
const token = await fetchToken(client.rpc, tokenAccount.address);
const immutableOwnerExtensionState = (
unwrapOption(token.data.extensions) ?? []
).find((item) => isExtension("ImmutableOwner", item));
console.log("Mint Address:", mint.address);
console.log("Token Account:", tokenAccount.address);
console.log("ImmutableOwner Extension:", immutableOwnerExtensionState);
console.log("SetAuthority Failure:", failure);
Console
Click to execute the code.

Web3.js

Instructions
import {
Connection,
Keypair,
sendAndConfirmTransaction,
SystemProgram,
Transaction,
LAMPORTS_PER_SOL
} from "@solana/web3.js";
import {
AuthorityType,
createInitializeMintInstruction,
createInitializeAccountInstruction,
createInitializeImmutableOwnerInstruction,
createSetAuthorityInstruction,
ExtensionType,
getAccount,
getAccountLen,
getImmutableOwner,
getMintLen,
TOKEN_2022_PROGRAM_ID
} from "@solana/spl-token";
const connection = new Connection("http://localhost:8899", "confirmed");
const feePayer = Keypair.generate();
const newOwner = Keypair.generate();
const airdropSignature = await connection.requestAirdrop(
feePayer.publicKey,
5 * LAMPORTS_PER_SOL
);
const latestBlockhash = await connection.getLatestBlockhash();
await connection.confirmTransaction({
blockhash: latestBlockhash.blockhash,
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
signature: airdropSignature
});
const mint = Keypair.generate();
const mintLength = getMintLen([]);
const mintRent = await connection.getMinimumBalanceForRentExemption(mintLength);
const extensions = [ExtensionType.ImmutableOwner];
const tokenAccount = new Keypair();
const tokenAccountLen = getAccountLen(extensions);
const tokenAccountRent =
await connection.getMinimumBalanceForRentExemption(tokenAccountLen);
const createMintAccountInstruction = SystemProgram.createAccount({
fromPubkey: feePayer.publicKey, // Account funding the new mint account.
newAccountPubkey: mint.publicKey, // New mint account to create.
space: mintLength, // Account size in bytes for the mint.
lamports: mintRent, // Lamports funding the mint account rent.
programId: TOKEN_2022_PROGRAM_ID // Program that owns the mint account.
});
const initializeMintInstruction = createInitializeMintInstruction(
mint.publicKey, // Mint account to initialize.
0, // Number of decimals for the token.
feePayer.publicKey, // Authority allowed to mint new tokens.
feePayer.publicKey, // Authority allowed to freeze token accounts.
TOKEN_2022_PROGRAM_ID // Program that owns the mint account.
);
const createTokenAccountInstruction = SystemProgram.createAccount({
fromPubkey: feePayer.publicKey, // Account funding the new token account.
newAccountPubkey: tokenAccount.publicKey, // New token account to create.
space: tokenAccountLen, // Account size in bytes for the token account plus ImmutableOwner.
lamports: tokenAccountRent, // Lamports funding the token account rent.
programId: TOKEN_2022_PROGRAM_ID // Program that owns the token account.
});
const initializeImmutableOwnerInstruction =
createInitializeImmutableOwnerInstruction(
tokenAccount.publicKey, // Token account that stores the ImmutableOwner extension.
TOKEN_2022_PROGRAM_ID // Token program that owns the token account.
);
const initializeTokenAccountInstruction = createInitializeAccountInstruction(
tokenAccount.publicKey, // Token account to initialize.
mint.publicKey, // Mint for the token account.
feePayer.publicKey, // Owner of the token account.
TOKEN_2022_PROGRAM_ID // Token program that owns the token account.
);
await sendAndConfirmTransaction(
connection,
new Transaction().add(
createMintAccountInstruction,
initializeMintInstruction
),
[feePayer, mint]
);
await sendAndConfirmTransaction(
connection,
new Transaction().add(
createTokenAccountInstruction,
initializeImmutableOwnerInstruction,
initializeTokenAccountInstruction
),
[feePayer, tokenAccount]
);
let failure: string | undefined;
try {
await sendAndConfirmTransaction(
connection,
new Transaction().add(
createSetAuthorityInstruction(
tokenAccount.publicKey, // Token account whose authority is being updated.
feePayer.publicKey, // Current token account owner signing the instruction.
AuthorityType.AccountOwner, // Account authority field to change.
newOwner.publicKey, // New token account owner to set.
[], // Additional multisig signers.
TOKEN_2022_PROGRAM_ID // Token program that owns the token account.
)
),
[feePayer]
);
} catch (error) {
failure = String(error);
}
if (!failure) {
throw new Error("Expected the owner change to fail");
}
const tokenAccountData = await getAccount(
connection,
tokenAccount.publicKey,
"confirmed",
TOKEN_2022_PROGRAM_ID
);
const immutableOwnerExtension = getImmutableOwner(tokenAccountData);
console.log("Mint Address:", mint.publicKey.toBase58());
console.log("Token Account:", tokenAccount.publicKey.toBase58());
console.log("ImmutableOwner Extension:", immutableOwnerExtension);
console.log("SetAuthority Failure:", failure);
Console
Click to execute the code.

Rust

Rust
use anyhow::{anyhow, Result};
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_commitment_config::CommitmentConfig;
use solana_sdk::{
program_pack::Pack,
signature::{Keypair, Signer},
transaction::Transaction,
};
use solana_system_interface::instruction::create_account;
use spl_token_2022_interface::{
extension::{
immutable_owner::ImmutableOwner, BaseStateWithExtensions, ExtensionType,
StateWithExtensions,
},
instruction::{
initialize_account, initialize_immutable_owner, initialize_mint, 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 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 = Mint::LEN;
let mint_rent = client
.get_minimum_balance_for_rent_exemption(mint_space)
.await?;
let create_mint_account_instruction = create_account(
&fee_payer.pubkey(), // Account funding the new mint account.
&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.
&TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.
);
let initialize_mint_instruction = 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.
)?;
let token_account = Keypair::new();
let token_account_space =
ExtensionType::try_calculate_account_len::<Account>(&[ExtensionType::ImmutableOwner])?;
let token_account_rent = client
.get_minimum_balance_for_rent_exemption(token_account_space)
.await?;
let create_token_account_instruction = create_account(
&fee_payer.pubkey(), // Account funding the new token account.
&token_account.pubkey(), // New token account to create.
token_account_rent, // Lamports funding the token account rent.
token_account_space as u64, // Account size in bytes for the token account plus ImmutableOwner.
&TOKEN_2022_PROGRAM_ID, // Program that owns the token account.
);
let initialize_token_account = initialize_account(
&TOKEN_2022_PROGRAM_ID, // Token program that owns the token account.
&token_account.pubkey(), // Token account to initialize.
&mint.pubkey(), // Mint for the token account.
&fee_payer.pubkey(), // Owner of the token account.
)?;
let init_immutable_owner_instruction =
initialize_immutable_owner(
&TOKEN_2022_PROGRAM_ID, // Token program that owns the token account.
&token_account.pubkey(), // Token account that stores the ImmutableOwner extension.
)?;
let transaction = Transaction::new_signed_with_payer(
&[
create_mint_account_instruction,
initialize_mint_instruction,
create_token_account_instruction,
init_immutable_owner_instruction,
initialize_token_account,
],
Some(&fee_payer.pubkey()),
&[&fee_payer, &mint, &token_account],
client.get_latest_blockhash().await?,
);
client.send_and_confirm_transaction(&transaction).await?;
let token_account_data = client.get_account(&token_account.pubkey()).await?;
let token_account_state = StateWithExtensions::<Account>::unpack(&token_account_data.data)?;
let new_owner = Keypair::new();
let set_authority_instruction = set_authority(
&TOKEN_2022_PROGRAM_ID, // Token program that owns the token account.
&token_account.pubkey(), // Token account whose authority is being updated.
Some(&new_owner.pubkey()), // New token account owner to set.
AuthorityType::AccountOwner, // Account authority field to change.
&fee_payer.pubkey(), // Current token account owner signing the instruction.
&[], // Additional multisig signers.
)?;
let set_authority_transaction = Transaction::new_signed_with_payer(
&[set_authority_instruction],
Some(&fee_payer.pubkey()),
&[&fee_payer],
client.get_latest_blockhash().await?,
);
let failure = match client
.send_and_confirm_transaction(&set_authority_transaction)
.await
{
Ok(sig) => return Err(anyhow!("Expected the owner change to fail: {}", sig)),
Err(e) => format!("{:#?}", e),
};
let immutable_owner_extension = token_account_state.get_extension::<ImmutableOwner>()?;
println!("Mint Address: {}", mint.pubkey());
println!("Token Account: {}", token_account.pubkey());
println!("ImmutableOwner Extension: {:#?}", immutable_owner_extension);
println!("SetAuthority Failure: {}", failure);
Ok(())
}
Console
Click to execute the code.

Is this page helpful?

目次

ページを編集

管理運営

© 2026 Solana Foundation.
無断転載を禁じます。
つながろう