일시 정지 가능한 민트

PausableExtension을 활성화하는 방법

PausableExtension는 지정된 일시 정지 권한이 민트의 모든 토큰 활동을 중단할 수 있도록 합니다. 일시 정지 권한은 언제든지 민트를 일시 정지하거나 재개할 수 있습니다.

일시 정지된 경우, Token-2022 프로그램은 해당 토큰에 대한 모든 전송, 민팅 및 소각을 거부합니다. 일시 정지가 해제되면 정상 작업이 재개됩니다.

이는 블록체인 토큰 시스템과 전통적인 금융 모두에서 일반적인 패턴으로, 규정 준수, 사고 대응 또는 관리 제어를 위해 활동을 동결하는 기능이 필요합니다.

Typescript

import {
airdropFactory,
appendTransactionMessageInstructions,
assertIsTransactionWithBlockhashLifetime,
createSolanaRpc,
createSolanaRpcSubscriptions,
createTransactionMessage,
generateKeyPairSigner,
getSignatureFromTransaction,
lamports,
pipe,
sendAndConfirmTransactionFactory,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
signTransactionMessageWithSigners
} from "@solana/kit";
import { getCreateAccountInstruction } from "@solana-program/system";
import {
extension,
fetchMint,
findAssociatedTokenPda,
getCreateAssociatedTokenInstructionAsync,
getInitializeMintInstruction,
getInitializePausableConfigInstruction,
getMintSize,
getMintToInstruction,
getPauseInstruction,
getResumeInstruction,
getTransferCheckedInstruction,
TOKEN_2022_PROGRAM_ADDRESS
} from "@solana-program/token-2022";
// Create Connection, local validator in this example
const rpc = createSolanaRpc("http://localhost:8899");
const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");
const sendAndConfirm = sendAndConfirmTransactionFactory({
rpc,
rpcSubscriptions
});
// Generate the authority (fee payer, mint authority, freeze authority, pause authority)
const authority = await generateKeyPairSigner();
// Fund authority/fee payer
await airdropFactory({ rpc, rpcSubscriptions })({
recipientAddress: authority.address,
lamports: lamports(5_000_000_000n), // 5 SOL
commitment: "confirmed"
});
// ---------- Create mint with Pausable extension ----------
const mint = await generateKeyPairSigner();
// Define the pausable extension for size calculation
const pausableExtension = extension("PausableConfig", {
authority: authority.address,
paused: false
});
// Calculate space for mint account with the Pausable extension
const space = BigInt(getMintSize([pausableExtension]));
// Get minimum balance for rent exemption
const rent = await rpc.getMinimumBalanceForRentExemption(space).send();
// Get latest blockhash
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
// 1) Create the mint account
const createMintAccountInstruction = getCreateAccountInstruction({
payer: authority,
newAccount: mint,
lamports: rent,
space,
programAddress: TOKEN_2022_PROGRAM_ADDRESS
});
// 2) Initialize the pausable extension (must come BEFORE initializeMint)
const initializePausableInstruction = getInitializePausableConfigInstruction({
mint: mint.address,
authority: authority.address
});
// 3) Initialize the mint
const initializeMintInstruction = getInitializeMintInstruction({
mint: mint.address,
decimals: 9,
mintAuthority: authority.address,
freezeAuthority: authority.address
});
// 4) Create associated token account for authority
const [authorityAta] = await findAssociatedTokenPda({
mint: mint.address,
owner: authority.address,
tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
});
const createAtaInstruction = await getCreateAssociatedTokenInstructionAsync({
payer: authority,
mint: mint.address,
owner: authority.address
});
// 5) Mint 100 tokens (100 * 10^9)
const mintToInstruction = getMintToInstruction({
mint: mint.address,
token: authorityAta,
mintAuthority: authority,
amount: 100_000_000_000n
});
// 6) Create recipient and their ATA
const recipient = await generateKeyPairSigner();
const [recipientAta] = await findAssociatedTokenPda({
mint: mint.address,
owner: recipient.address,
tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
});
const createRecipientAtaInstruction =
await getCreateAssociatedTokenInstructionAsync({
payer: authority,
mint: mint.address,
owner: recipient.address
});
// Send transaction to create mint with pausable extension, ATAs, and mint tokens
const createMintTx = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(authority, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) =>
appendTransactionMessageInstructions(
[
createMintAccountInstruction,
initializePausableInstruction,
initializeMintInstruction,
createAtaInstruction,
mintToInstruction,
createRecipientAtaInstruction
],
tx
)
);
const signedCreateMintTx =
await signTransactionMessageWithSigners(createMintTx);
assertIsTransactionWithBlockhashLifetime(signedCreateMintTx);
await sendAndConfirm(signedCreateMintTx, {
commitment: "confirmed",
skipPreflight: true
});
console.log("Mint created with Pausable extension:", mint.address);
// Read back the pausable config from the mint
let mintAccount = await fetchMint(rpc, mint.address);
console.log("Pausable config:", mintAccount.data.extensions);
// ---------- Pause the mint ----------
const pauseInstruction = getPauseInstruction({
mint: mint.address,
authority: authority
});
const { value: latestBlockhash2 } = await rpc.getLatestBlockhash().send();
const pauseTx = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(authority, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash2, tx),
(tx) => appendTransactionMessageInstructions([pauseInstruction], tx)
);
const signedPauseTx = await signTransactionMessageWithSigners(pauseTx);
assertIsTransactionWithBlockhashLifetime(signedPauseTx);
await sendAndConfirm(signedPauseTx, {
commitment: "confirmed"
});
console.log("\nMint is now PAUSED");
// Verify paused state
mintAccount = await fetchMint(rpc, mint.address);
console.log("Pausable config:", mintAccount.data.extensions);
// Try a transfer while paused (should fail)
const transferInstruction = getTransferCheckedInstruction({
source: authorityAta,
mint: mint.address,
destination: recipientAta,
authority: authority,
amount: 1_000_000_000n, // 1 token
decimals: 9
});
const { value: latestBlockhash3 } = await rpc.getLatestBlockhash().send();
const transferWhilePausedTx = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(authority, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash3, tx),
(tx) => appendTransactionMessageInstructions([transferInstruction], tx)
);
const signedTransferWhilePausedTx = await signTransactionMessageWithSigners(
transferWhilePausedTx
);
console.log("\nTransfer Expected to Fail with Error:");
try {
assertIsTransactionWithBlockhashLifetime(signedTransferWhilePausedTx);
await sendAndConfirm(signedTransferWhilePausedTx, {
commitment: "confirmed"
});
} catch (error: any) {
console.log(" ", error.message ?? error);
}
// ---------- Resume (unpause) the mint ----------
const resumeInstruction = getResumeInstruction({
mint: mint.address,
authority: authority
});
const { value: latestBlockhash4 } = await rpc.getLatestBlockhash().send();
const resumeTx = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(authority, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash4, tx),
(tx) => appendTransactionMessageInstructions([resumeInstruction], tx)
);
const signedResumeTx = await signTransactionMessageWithSigners(resumeTx);
assertIsTransactionWithBlockhashLifetime(signedResumeTx);
await sendAndConfirm(signedResumeTx, {
commitment: "confirmed"
});
console.log("\nMint is now RESUMED");
// Verify resumed state
mintAccount = await fetchMint(rpc, mint.address);
console.log("Pausable config:", mintAccount.data.extensions);
// Transfer should now succeed after resume
const { value: latestBlockhash5 } = await rpc.getLatestBlockhash().send();
const transferAfterResumeTx = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(authority, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash5, tx),
(tx) => appendTransactionMessageInstructions([transferInstruction], tx)
);
const signedTransferAfterResumeTx = await signTransactionMessageWithSigners(
transferAfterResumeTx
);
assertIsTransactionWithBlockhashLifetime(signedTransferAfterResumeTx);
await sendAndConfirm(signedTransferAfterResumeTx, {
commitment: "confirmed"
});
const transferSig = getSignatureFromTransaction(signedTransferAfterResumeTx);
console.log("\nTransfer after resume succeeded!");
console.log("Transaction Signature:", transferSig);
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::{
pausable::{instruction as pausable_ix, PausableConfig},
BaseStateWithExtensions, ExtensionType, StateWithExtensions,
},
instruction::{initialize_mint, mint_to, 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 latest_blockhash = client.get_latest_blockhash().await?;
let fee_payer = Keypair::new();
// Airdrop SOL to fee payer
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;
}
}
// ---------- Create mint with Pausable extension ----------
let mint = Keypair::new();
// Calculate space for mint account with the Pausable extension
let mint_space = ExtensionType::try_calculate_account_len::<Mint>(&[ExtensionType::Pausable])?;
let mint_rent = client
.get_minimum_balance_for_rent_exemption(mint_space)
.await?;
// 1) Create the mint account
let create_mint_account_ix = create_account(
&fee_payer.pubkey(),
&mint.pubkey(),
mint_rent,
mint_space as u64,
&TOKEN_2022_PROGRAM_ID,
);
// 2) Initialize the pausable extension (must come BEFORE initialize_mint)
let initialize_pausable_ix = pausable_ix::initialize(
&TOKEN_2022_PROGRAM_ID,
&mint.pubkey(),
&fee_payer.pubkey(), // pause authority
)?;
// 3) Initialize the mint
let initialize_mint_ix = initialize_mint(
&TOKEN_2022_PROGRAM_ID,
&mint.pubkey(),
&fee_payer.pubkey(), // mint authority
Some(&fee_payer.pubkey()), // freeze authority
9, // decimals
)?;
// 4) Create associated token account for fee_payer
let ata = get_associated_token_address_with_program_id(
&fee_payer.pubkey(),
&mint.pubkey(),
&TOKEN_2022_PROGRAM_ID,
);
let create_ata_ix = create_associated_token_account(
&fee_payer.pubkey(),
&fee_payer.pubkey(),
&mint.pubkey(),
&TOKEN_2022_PROGRAM_ID,
);
// 5) Mint 100 tokens
let amount = 100_000_000_000; // 100 tokens with 9 decimals
let mint_to_ix = mint_to(
&TOKEN_2022_PROGRAM_ID,
&mint.pubkey(),
&ata,
&fee_payer.pubkey(),
&[&fee_payer.pubkey()],
amount,
)?;
// 6) Create recipient ATA
let recipient = Keypair::new();
let recipient_ata = get_associated_token_address_with_program_id(
&recipient.pubkey(),
&mint.pubkey(),
&TOKEN_2022_PROGRAM_ID,
);
let create_recipient_ata_ix = create_associated_token_account(
&fee_payer.pubkey(),
&recipient.pubkey(),
&mint.pubkey(),
&TOKEN_2022_PROGRAM_ID,
);
let tx = Transaction::new_signed_with_payer(
&[
create_mint_account_ix,
initialize_pausable_ix,
initialize_mint_ix,
create_ata_ix,
mint_to_ix,
create_recipient_ata_ix,
],
Some(&fee_payer.pubkey()),
&[&fee_payer, &mint],
latest_blockhash,
);
client.send_and_confirm_transaction(&tx).await?;
println!("Mint created with Pausable extension: {}", mint.pubkey());
// Read back the pausable config from the mint
let mint_data = client.get_account(&mint.pubkey()).await?;
let mint_state = StateWithExtensions::<Mint>::unpack(&mint_data.data)?;
let pausable = mint_state.get_extension::<PausableConfig>()?;
println!("Pausable config: {:#?}", pausable);
// ---------- Pause the mint ----------
let pause_ix = pausable_ix::pause(
&TOKEN_2022_PROGRAM_ID,
&mint.pubkey(),
&fee_payer.pubkey(), // pause authority
&[&fee_payer.pubkey()],
)?;
let latest_blockhash = client.get_latest_blockhash().await?;
let tx = Transaction::new_signed_with_payer(
&[pause_ix],
Some(&fee_payer.pubkey()),
&[&fee_payer],
latest_blockhash,
);
client.send_and_confirm_transaction(&tx).await?;
println!("\nMint is now PAUSED");
// Verify paused state
let mint_data = client.get_account(&mint.pubkey()).await?;
let mint_state = StateWithExtensions::<Mint>::unpack(&mint_data.data)?;
let pausable = mint_state.get_extension::<PausableConfig>()?;
println!("Pausable config: {:#?}", pausable);
// Try a transfer while paused (should fail)
let transfer_ix = transfer_checked(
&TOKEN_2022_PROGRAM_ID,
&ata,
&mint.pubkey(),
&recipient_ata,
&fee_payer.pubkey(),
&[&fee_payer.pubkey()],
1_000_000_000, // 1 token
9,
)?;
let latest_blockhash = client.get_latest_blockhash().await?;
let tx = Transaction::new_signed_with_payer(
&[transfer_ix.clone()],
Some(&fee_payer.pubkey()),
&[&fee_payer],
latest_blockhash,
);
println!("\nTransfer Expected to Fail with Error:");
let result = client.simulate_transaction(&tx).await?;
if result.value.err.is_some() {
if let Some(logs) = result.value.logs {
for log in logs.iter().filter(|l| l.starts_with("Program log:")) {
println!(" {}", log);
}
}
}
// ---------- Resume (unpause) the mint ----------
let resume_ix = pausable_ix::resume(
&TOKEN_2022_PROGRAM_ID,
&mint.pubkey(),
&fee_payer.pubkey(), // pause authority
&[&fee_payer.pubkey()],
)?;
let latest_blockhash = client.get_latest_blockhash().await?;
let tx = Transaction::new_signed_with_payer(
&[resume_ix],
Some(&fee_payer.pubkey()),
&[&fee_payer],
latest_blockhash,
);
client.send_and_confirm_transaction(&tx).await?;
println!("\nMint is now RESUMED");
// Verify resumed state
let mint_data = client.get_account(&mint.pubkey()).await?;
let mint_state = StateWithExtensions::<Mint>::unpack(&mint_data.data)?;
let pausable = mint_state.get_extension::<PausableConfig>()?;
println!("Pausable config: {:#?}", pausable);
// Transfer should now succeed
let latest_blockhash = client.get_latest_blockhash().await?;
let tx = Transaction::new_signed_with_payer(
&[transfer_ix],
Some(&fee_payer.pubkey()),
&[&fee_payer],
latest_blockhash,
);
let sig = client.send_and_confirm_transaction(&tx).await?;
println!("\nTransfer after resume succeeded!");
println!("Transaction Signature: {}", sig);
// Verify recipient received the tokens
let recipient_data = client.get_account(&recipient_ata).await?;
let recipient_state = StateWithExtensions::<Account>::unpack(&recipient_data.data)?;
println!("\nRecipient Token Account: {:#?}", recipient_state.base);
Ok(())
}
Console
Click to execute the code.

Is this page helpful?

목차

페이지 편집

관리자

© 2026 솔라나 재단.
모든 권리 보유.
연결하기