CPI Guard

Что такое CPI Guard?

Расширение аккаунта CpiGuard в Token Extensions Program предотвращает использование token account другой онлайн-программой для определенных инструкций токенов через Cross Program Invocation (CPI).

Когда CPI guard включен, Token Extensions Program отклоняет эти инструкции, когда другая онлайн-программа вызывает их через CPI:

  • Transfer, TransferChecked и TransferCheckedWithFee, когда владелец token account используется в качестве авторизации инструкции
  • Burn и BurnChecked, когда владелец token account используется в качестве авторизации инструкции
  • Approve и ApproveChecked
  • CloseAccount, когда lamport будут отправлены куда-либо, кроме владельца token account
  • SetAuthority при добавлении или замене полномочий на закрытие

Когда CPI guard включен, эти случаи все еще разрешены:

  • Transfer, TransferChecked, TransferCheckedWithFee, Burn и BurnChecked через CPI, когда делегат или постоянный делегат подписывает вместо владельца token account
  • CloseAccount через CPI, когда lamport отправляются владельцу token account
  • SetAuthority для удаления существующих полномочий на закрытие
  • Revoke

Когда владелец token account используется в качестве авторизации инструкции, инструкции Token Extensions Program все еще могут быть обработаны, если инструкция добавлена непосредственно в транзакцию, а не вызвана через CPI. Изменение владельца token account с помощью SetAuthority остается заблокированным, пока включен CPI guard, даже вне CPI. EnableCpiGuard и DisableCpiGuard также не могут быть вызваны через CPI.

Как включить CPI Guard

Чтобы включить CPI guard:

  1. Создайте и инициализируйте mint.
  2. Рассчитайте размер токен-аккаунта и rent, необходимый для токен-аккаунта с CpiGuard.
  3. Создайте и инициализируйте токен-аккаунт для mint.
  4. Вызовите EnableCpiGuard на токен-аккаунте.
  5. Вызовите DisableCpiGuard на токен-аккаунте, чтобы снова разрешить эти инструкции через CPI.

Создание и инициализация mint

Создайте и инициализируйте mint, который будет использовать токен-аккаунт.

Расчет размера токен-аккаунта

Рассчитайте размер токен-аккаунта для базового токен-аккаунта плюс расширение CpiGuard. Этот размер используется в CreateAccount.

Расчет rent

Рассчитайте rent, используя размер токен-аккаунта, необходимый для расширения CpiGuard.

Создание и инициализация токен-аккаунта

Создайте токен-аккаунт с рассчитанным пространством и lamports, затем инициализируйте его для mint.

Включение CPI guard

Включите CpiGuard на токен-аккаунте.

Отключение CPI guard

Отключите CpiGuard на токен-аккаунте.

Создание и инициализация mint

Создайте и инициализируйте mint, который будет использовать токен-аккаунт.

Расчет размера токен-аккаунта

Рассчитайте размер токен-аккаунта для базового токен-аккаунта плюс расширение CpiGuard. Этот размер используется в CreateAccount.

Расчет rent

Рассчитайте rent, используя размер токен-аккаунта, необходимый для расширения CpiGuard.

Создание и инициализация токен-аккаунта

Создайте токен-аккаунт с рассчитанным пространством и lamports, затем инициализируйте его для mint.

Включение CPI guard

Включите CpiGuard на токен-аккаунте.

Отключение CPI guard

Отключите CpiGuard на токен-аккаунте.

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

Справочник по исходному коду

ЭлементОписаниеИсточник
CpiGuardРасширение аккаунта, которое хранит информацию о том, включена ли в данный момент защита CPI для token account.Источник
CpiGuardInstruction::EnableИнструкция, которая включает защиту CPI для token account.Источник
CpiGuardInstruction::DisableИнструкция, которая отключает защиту CPI для token account.Источник
process_toggle_cpi_guardЛогика процессора, используемая Enable и Disable, которая проверяет владение аккаунтом, отклоняет изменения защиты через CPI и инициализирует CpiGuard, если он еще не существует, перед установкой lock_cpi.Источник

Typescript

Приведенный ниже пример Kit использует сгенерированные инструкции напрямую. Устаревшие примеры с использованием @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 {
extension,
fetchToken,
getDisableCpiGuardInstruction,
getEnableCpiGuardInstruction,
getInitializeAccountInstruction,
getInitializeMintInstruction,
getMintSize,
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 mintSpace = BigInt(getMintSize());
const mintRent = await client.rpc
.getMinimumBalanceForRentExemption(mintSpace)
.send();
await client.sendTransaction([
getCreateAccountInstruction({
payer: client.payer, // Account funding account creation.
newAccount: mint, // New mint account to create.
lamports: mintRent, // Lamports funding the mint account rent.
space: mintSpace, // Account size in bytes for the mint account.
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 cpiGuardExtension = extension("CpiGuard", {
lockCpi: false
});
const tokenSpace = BigInt(getTokenSize([cpiGuardExtension]));
const tokenRent = await client.rpc
.getMinimumBalanceForRentExemption(tokenSpace)
.send();
await client.sendTransaction([
getCreateAccountInstruction({
payer: client.payer, // Account funding account creation.
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 CpiGuard.
programAddress: TOKEN_2022_PROGRAM_ADDRESS // Program that owns the token account.
}),
getInitializeAccountInstruction({
account: tokenAccount.address, // Token account to initialize.
mint: mint.address, // Mint for the token account.
owner: client.payer.address // Owner allowed to control the token account.
})
]);
await client.sendTransaction([
getEnableCpiGuardInstruction({
token: tokenAccount.address, // Token account that stores the CpiGuard extension.
owner: client.payer // Token account owner authorized to enable CPI guard.
})
]);
const enabledTokenAccount = await fetchToken(client.rpc, tokenAccount.address);
const enabledCpiGuard = (
unwrapOption(enabledTokenAccount.data.extensions) ?? []
).find((item) => isExtension("CpiGuard", item));
await client.sendTransaction([
getDisableCpiGuardInstruction({
token: tokenAccount.address, // Token account that stores the CpiGuard extension.
owner: client.payer // Token account owner authorized to disable CPI guard.
})
]);
const disabledTokenAccount = await fetchToken(client.rpc, tokenAccount.address);
const disabledCpiGuard = (
unwrapOption(disabledTokenAccount.data.extensions) ?? []
).find((item) => isExtension("CpiGuard", item));
console.log("\nMint Address:", mint.address);
console.log("\nToken Account:", tokenAccount.address);
console.log("\nEnabled CPI Guard:", enabledCpiGuard);
console.log("\nDisabled CPI Guard:", disabledCpiGuard);
Console
Click to execute the code.

Web3.js

Instructions
import {
Connection,
Keypair,
LAMPORTS_PER_SOL,
sendAndConfirmTransaction,
SystemProgram,
Transaction
} from "@solana/web3.js";
import {
createDisableCpiGuardInstruction,
createEnableCpiGuardInstruction,
createInitializeAccountInstruction,
createInitializeMintInstruction,
ExtensionType,
getAccountLen,
getAccount,
getCpiGuard,
getMintLen,
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 mint = Keypair.generate();
const tokenAccount = Keypair.generate();
const airdropSignature = await connection.requestAirdrop(
feePayer.publicKey,
5 * LAMPORTS_PER_SOL
);
await connection.confirmTransaction({
blockhash: latestBlockhash.blockhash,
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
signature: airdropSignature
});
const mintSpace = getMintLen([]);
const mintRent = await connection.getMinimumBalanceForRentExemption(mintSpace);
await sendAndConfirmTransaction(
connection,
new Transaction().add(
SystemProgram.createAccount({
fromPubkey: feePayer.publicKey, // Account funding account creation.
newAccountPubkey: mint.publicKey, // New mint account to create.
space: mintSpace, // Account size in bytes for the mint account.
lamports: mintRent, // Lamports funding the mint account rent.
programId: TOKEN_2022_PROGRAM_ID // Program that owns the mint account.
}),
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.
)
),
[feePayer, mint],
{ commitment: "confirmed" }
);
const tokenAccountSpace = getAccountLen([ExtensionType.CpiGuard]);
const tokenAccountRent =
await connection.getMinimumBalanceForRentExemption(tokenAccountSpace);
await sendAndConfirmTransaction(
connection,
new Transaction().add(
SystemProgram.createAccount({
fromPubkey: feePayer.publicKey, // Account funding account creation.
newAccountPubkey: tokenAccount.publicKey, // New token account to create.
space: tokenAccountSpace, // Account size in bytes for the token account plus CpiGuard.
lamports: tokenAccountRent, // Lamports funding the token account rent.
programId: TOKEN_2022_PROGRAM_ID // Program that owns the token account.
}),
createInitializeAccountInstruction(
tokenAccount.publicKey, // Token account to initialize.
mint.publicKey, // Mint for the token account.
feePayer.publicKey, // Owner allowed to control the token account.
TOKEN_2022_PROGRAM_ID // Program that owns the token account.
)
),
[feePayer, tokenAccount],
{ commitment: "confirmed" }
);
await sendAndConfirmTransaction(
connection,
new Transaction().add(
createEnableCpiGuardInstruction(
tokenAccount.publicKey, // Token account that stores the CpiGuard extension.
feePayer.publicKey, // Token account owner authorized to enable CPI guard.
[], // Additional multisig signers.
TOKEN_2022_PROGRAM_ID // Token program that owns the token account.
)
),
[feePayer],
{ commitment: "confirmed" }
);
const enabledTokenAccount = await getAccount(
connection,
tokenAccount.publicKey,
"confirmed",
TOKEN_2022_PROGRAM_ID
);
const enabledCpiGuard = getCpiGuard(enabledTokenAccount);
await sendAndConfirmTransaction(
connection,
new Transaction().add(
createDisableCpiGuardInstruction(
tokenAccount.publicKey, // Token account that stores the CpiGuard extension.
feePayer.publicKey, // Token account owner authorized to disable CPI guard.
[], // Additional multisig signers.
TOKEN_2022_PROGRAM_ID // Token program that owns the token account.
)
),
[feePayer],
{ commitment: "confirmed" }
);
const disabledTokenAccount = await getAccount(
connection,
tokenAccount.publicKey,
"confirmed",
TOKEN_2022_PROGRAM_ID
);
const disabledCpiGuard = getCpiGuard(disabledTokenAccount);
console.log("\nMint Address:", mint.publicKey.toBase58());
console.log("\nToken Account:", tokenAccount.publicKey.toBase58());
console.log("\nEnabled CPI Guard:", enabledCpiGuard);
console.log("\nDisabled CPI Guard:", disabledCpiGuard);
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::{
program_pack::Pack,
signature::{Keypair, Signer},
transaction::Transaction,
};
use solana_system_interface::instruction::create_account;
use spl_token_2022_interface::{
extension::{
cpi_guard::{
instruction::{disable_cpi_guard, enable_cpi_guard},
CpiGuard,
},
BaseStateWithExtensions, ExtensionType, StateWithExtensions,
},
instruction::{initialize_account, initialize_mint},
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 token_account = Keypair::new();
let mint_space = Mint::LEN;
let mint_rent = client
.get_minimum_balance_for_rent_exemption(mint_space)
.await?;
let create_mint_transaction = Transaction::new_signed_with_payer(
&[
create_account(
&fee_payer.pubkey(), // Account funding account creation.
&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 account.
&TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.
),
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.
)?,
],
Some(&fee_payer.pubkey()),
&[&fee_payer, &mint],
client.get_latest_blockhash().await?,
);
client
.send_and_confirm_transaction(&create_mint_transaction)
.await?;
let token_account_space =
ExtensionType::try_calculate_account_len::<Account>(&[ExtensionType::CpiGuard])?;
let token_account_rent = client
.get_minimum_balance_for_rent_exemption(token_account_space)
.await?;
let create_token_account_transaction = Transaction::new_signed_with_payer(
&[
create_account(
&fee_payer.pubkey(), // Account funding account creation.
&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 CpiGuard.
&TOKEN_2022_PROGRAM_ID, // Program that owns the token account.
),
initialize_account(
&TOKEN_2022_PROGRAM_ID, // Program that owns the token account.
&token_account.pubkey(), // Token account to initialize.
&mint.pubkey(), // Mint for the token account.
&fee_payer.pubkey(), // Owner allowed to control the token account.
)?,
],
Some(&fee_payer.pubkey()),
&[&fee_payer, &token_account],
client.get_latest_blockhash().await?,
);
client
.send_and_confirm_transaction(&create_token_account_transaction)
.await?;
let enable_cpi_guard_instruction = enable_cpi_guard(
&TOKEN_2022_PROGRAM_ID, // Token program that owns the token account.
&token_account.pubkey(), // Token account that stores the CpiGuard extension.
&fee_payer.pubkey(), // Token account owner authorized to enable CPI guard.
&[], // Additional multisig signers.
)?;
let enable_transaction = Transaction::new_signed_with_payer(
&[enable_cpi_guard_instruction],
Some(&fee_payer.pubkey()),
&[&fee_payer],
client.get_latest_blockhash().await?,
);
client
.send_and_confirm_transaction(&enable_transaction)
.await?;
let enabled_token_account_data = client.get_account(&token_account.pubkey()).await?;
let enabled_token_account_state =
StateWithExtensions::<Account>::unpack(&enabled_token_account_data.data)?;
let enabled_cpi_guard = enabled_token_account_state.get_extension::<CpiGuard>()?;
let disable_cpi_guard_instruction = disable_cpi_guard(
&TOKEN_2022_PROGRAM_ID, // Token program that owns the token account.
&token_account.pubkey(), // Token account that stores the CpiGuard extension.
&fee_payer.pubkey(), // Token account owner authorized to disable CPI guard.
&[], // Additional multisig signers.
)?;
let disable_transaction = Transaction::new_signed_with_payer(
&[disable_cpi_guard_instruction],
Some(&fee_payer.pubkey()),
&[&fee_payer],
client.get_latest_blockhash().await?,
);
client
.send_and_confirm_transaction(&disable_transaction)
.await?;
let disabled_token_account_data = client.get_account(&token_account.pubkey()).await?;
let disabled_token_account_state =
StateWithExtensions::<Account>::unpack(&disabled_token_account_data.data)?;
let disabled_cpi_guard = disabled_token_account_state.get_extension::<CpiGuard>()?;
println!("\nMint Address: {}", mint.pubkey());
println!("\nToken Account: {}", token_account.pubkey());
println!("\nEnabled CPI Guard: {:#?}", enabled_cpi_guard);
println!("\nDisabled CPI Guard: {:#?}", disabled_cpi_guard);
Ok(())
}
Console
Click to execute the code.

Is this page helpful?

Содержание

Редактировать страницу

Управляется

© 2026 Solana Foundation.
Все права защищены.
Связаться с нами