Token-Gruppen und Mitglieder
Wie man die GroupPointerExtension und GroupMemberPointerExtension aktiviert
Die
GroupPointerExtension
kennzeichnet einen mint account als Gruppen-Mint (wie z.B. ein Collection-NFT),
indem sie auf ein Konto verweist, das
Gruppenkonfigurationen
wie Update-Berechtigung und maximale Größe enthält.
Die
GroupMemberPointerExtension
kennzeichnet einen mint account als Mitglieds-Mint (wie z.B. ein NFT in einer
Kollektion), indem sie auf ein Konto verweist, das
Mitgliedschaftsdaten
wie Gruppenadresse und Mitgliedsnummer enthält.
Das Token Extensions Program implementiert die Token Group Interface direkt, wodurch sowohl Gruppen- als auch Mitgliedsdaten auf dem mint account selbst gespeichert werden können. Wenn die Pointer-Extensions auf den Mint selbst verweisen, können die TokenGroup und TokenGroupMember Extensions verwendet werden, um alle Kollektions- und Mitgliedschaftsdaten im mint account zu speichern.
Typescript
import { getCreateAccountInstruction } from "@solana-program/system";import {extension,getInitializeAccountInstruction,getInitializeMintInstruction,getInitializeGroupPointerInstruction,getMintSize,TOKEN_2022_PROGRAM_ADDRESS,getInitializeTokenGroupInstruction,getInitializeGroupMemberPointerInstruction,getInitializeTokenGroupMemberInstruction} from "@solana-program/token-2022";import {airdropFactory,appendTransactionMessageInstructions,createSolanaRpc,createSolanaRpcSubscriptions,createTransactionMessage,generateKeyPairSigner,getSignatureFromTransaction,lamports,pipe,sendAndConfirmTransactionFactory,setTransactionMessageFeePayerSigner,setTransactionMessageLifetimeUsingBlockhash,signTransactionMessageWithSigners,some} from "@solana/kit";// Create Connection, local validator in this exampleconst rpc = createSolanaRpc("http://localhost:8899");const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");// Generate the authority for the mint (also acts as fee payer)const authority = await generateKeyPairSigner();// Fund authority/fee payerawait airdropFactory({ rpc, rpcSubscriptions })({recipientAddress: authority.address,lamports: lamports(5_000_000_000n), // 5 SOLcommitment: "confirmed"});// ===== GROUP MINT CREATION =====// Generate keypair to use as address of group mintconst groupMint = await generateKeyPairSigner();// Group pointer extension (points to external group account)const groupPointerExtension = extension("GroupPointer", {authority: authority.address,groupAddress: groupMint.address // Points to the group mint itself});// Group extension (stores group data directly on mint)const groupExtension = extension("TokenGroup", {updateAuthority: some(authority.address),mint: groupMint.address,size: 1, // Will be incremented when members are addedmaxSize: 10});// Get mint account size with group pointer extensionconst spaceWithGroupPointerExtensions = BigInt(getMintSize([groupPointerExtension]));// Get mint account size with group pointer and group extensionconst spaceWithGroupAndGroupPointerExtensions = BigInt(getMintSize([groupPointerExtension, groupExtension]));// Get rent exempt balance for group mintconst groupMintRent = await rpc.getMinimumBalanceForRentExemption(spaceWithGroupAndGroupPointerExtensions).send();// Get latest blockhashconst { value: latestBlockhash } = await rpc.getLatestBlockhash().send();// GROUP MINT INSTRUCTIONSconst createGroupMintAccountInstruction = getCreateAccountInstruction({payer: authority,newAccount: groupMint,lamports: groupMintRent,space: spaceWithGroupPointerExtensions,programAddress: TOKEN_2022_PROGRAM_ADDRESS});const initializeGroupPointerInstruction = getInitializeGroupPointerInstruction({mint: groupMint.address,authority: authority.address,groupAddress: groupMint.address});const initializeGroupMintInstruction = getInitializeMintInstruction({mint: groupMint.address,decimals: 0, // NFT-stylemintAuthority: authority.address,freezeAuthority: authority.address});const initializeGroupInstruction = getInitializeTokenGroupInstruction({group: groupMint.address,mint: groupMint.address,mintAuthority: authority,updateAuthority: authority.address,maxSize: 10});// Build group mint transactionconst groupInstructions = [createGroupMintAccountInstruction,initializeGroupPointerInstruction,initializeGroupMintInstruction,initializeGroupInstruction];const groupTransactionMessage = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(authority, tx),(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),(tx) => appendTransactionMessageInstructions(groupInstructions, tx));const signedGroupTransaction = await signTransactionMessageWithSigners(groupTransactionMessage);await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedGroupTransaction,{ commitment: "confirmed", skipPreflight: true });const groupTransactionSignature = getSignatureFromTransaction(signedGroupTransaction);console.log("Group Mint Address:", groupMint.address.toString());console.log("Group Transaction Signature:", groupTransactionSignature);// ===== MEMBER MINT CREATION =====// Generate keypair for member mintconst memberMint = await generateKeyPairSigner();// Member pointer extension (points to external member account)const memberPointerExtension = extension("GroupMemberPointer", {authority: authority.address,memberAddress: memberMint.address // Points to the member mint itself});// Member extension (stores membership data directly on mint)const memberExtension = extension("TokenGroupMember", {mint: memberMint.address,group: groupMint.address, // References the group mint we created abovememberNumber: 1 // First member of the group});// Get mint account size with group pointer extensionconst spaceWithMemberPointerExtension = BigInt(getMintSize([groupPointerExtension]));// Get mint account size with group pointer and group extensionconst spaceWithMemberAndMemberPointerExtensions = BigInt(getMintSize([groupPointerExtension, groupExtension]));// Get rent exempt balance for member mintconst memberMintRent = await rpc.getMinimumBalanceForRentExemption(spaceWithMemberAndMemberPointerExtensions).send();// Get fresh blockhash for member mint transactionconst { value: memberLatestBlockhash } = await rpc.getLatestBlockhash().send();// MEMBER MINT INSTRUCTIONSconst createMemberMintAccountInstruction = getCreateAccountInstruction({payer: authority,newAccount: memberMint,lamports: memberMintRent,space: spaceWithMemberPointerExtension, // Only pointer extension space for account creationprogramAddress: TOKEN_2022_PROGRAM_ADDRESS});const initializeMemberPointerInstruction =getInitializeGroupMemberPointerInstruction({mint: memberMint.address,authority: authority.address,memberAddress: memberMint.address});const initializeMemberMintInstruction = getInitializeMintInstruction({mint: memberMint.address,decimals: 0, // NFT-stylemintAuthority: authority.address,freezeAuthority: authority.address});// Initialize member extension (added post-mint initialization)const initializeMemberInstruction = getInitializeTokenGroupMemberInstruction({member: memberMint.address,memberMint: memberMint.address,memberMintAuthority: authority,group: groupMint.address, // References the group mintgroupUpdateAuthority: authority});// Build member mint transactionconst memberInstructions = [createMemberMintAccountInstruction,initializeMemberPointerInstruction,initializeMemberMintInstruction,initializeMemberInstruction];const memberTransactionMessage = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(authority, tx),(tx) =>setTransactionMessageLifetimeUsingBlockhash(memberLatestBlockhash, tx),(tx) => appendTransactionMessageInstructions(memberInstructions, tx));const signedMemberTransaction = await signTransactionMessageWithSigners(memberTransactionMessage);await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedMemberTransaction,{ commitment: "confirmed", skipPreflight: true });const memberTransactionSignature = getSignatureFromTransaction(signedMemberTransaction);console.log("Member Mint Address:", memberMint.address.toString());console.log("Member Transaction Signature:", memberTransactionSignature);console.log("\n===== SUMMARY =====");console.log("Group Mint:", groupMint.address.toString());console.log("Member Mint:", memberMint.address.toString());console.log("Member belongs to Group:", groupMint.address.toString());
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_token_2022_interface::{extension::{group_member_pointer::{instruction::initialize as initialize_group_member_pointer, GroupMemberPointer,},group_pointer::{instruction::initialize as initialize_group_pointer, GroupPointer},BaseStateWithExtensions, ExtensionType, StateWithExtensions,},instruction::initialize_mint,state::Mint,ID as TOKEN_2022_PROGRAM_ID,};use spl_token_group_interface::{instruction::{initialize_group, initialize_member},state::{TokenGroup, TokenGroupMember},};#[tokio::main]async fn main() -> Result<()> {// Create connection to local validatorlet client = RpcClient::new_with_commitment(String::from("http://localhost:8899"),CommitmentConfig::confirmed(),);let latest_blockhash = client.get_latest_blockhash().await?;// Generate the authority for the mint (also acts as fee payer)let authority = Keypair::new();// Airdrop 5 SOL to authority/fee payerlet airdrop_signature = client.request_airdrop(&authority.pubkey(), 5_000_000_000).await?;loop {let confirmed = client.confirm_transaction(&airdrop_signature).await?;if confirmed {break;}}// Create group mint// Generate keypair to use as address of group mintlet group_mint = Keypair::new();// Calculate space for mint with group pointer extension only (for account creation)let group_mint_space =ExtensionType::try_calculate_account_len::<Mint>(&[ExtensionType::GroupPointer])?;// Calculate space for mint with group pointer and group extension (for rent calculation)let group_mint_space_with_data = ExtensionType::try_calculate_account_len::<Mint>(&[ExtensionType::GroupPointer,ExtensionType::TokenGroup,])?;// Get rent exempt balance for full space (including data extension)let group_mint_rent = client.get_minimum_balance_for_rent_exemption(group_mint_space_with_data).await?;let create_group_mint_account_instruction = create_account(&authority.pubkey(), // payer&group_mint.pubkey(), // new account (mint)group_mint_rent, // lamportsgroup_mint_space as u64, // space (pointer only for account creation)&TOKEN_2022_PROGRAM_ID, // program id);let initialize_group_pointer_instruction = initialize_group_pointer(&TOKEN_2022_PROGRAM_ID,&group_mint.pubkey(), // mintSome(authority.pubkey()), // authoritySome(group_mint.pubkey()), // group address (points to itself))?;let initialize_group_mint_instruction = initialize_mint(&TOKEN_2022_PROGRAM_ID, // program id&group_mint.pubkey(), // mint&authority.pubkey(), // mint authoritySome(&authority.pubkey()), // freeze authority0, // decimals (NFT-style))?;// Initialize group extension (added post-mint initialization)let initialize_group_instruction = initialize_group(&TOKEN_2022_PROGRAM_ID, // program id&group_mint.pubkey(), // group&group_mint.pubkey(), // mint&authority.pubkey(), // mint authoritySome(authority.pubkey()), // update authority10, // max size);// Build group mint transactionlet group_transaction = Transaction::new_signed_with_payer(&[create_group_mint_account_instruction,initialize_group_pointer_instruction,initialize_group_mint_instruction,initialize_group_instruction,],Some(&authority.pubkey()),&[&authority, &group_mint],latest_blockhash,);client.send_and_confirm_transaction(&group_transaction).await?;// Generate keypair for member mintlet member_mint = Keypair::new();// Calculate space for mint with member pointer extension only (for account creation)let member_mint_space =ExtensionType::try_calculate_account_len::<Mint>(&[ExtensionType::GroupMemberPointer])?;// Calculate space for mint with member pointer and member extension (for rent calculation)let member_mint_space_with_data = ExtensionType::try_calculate_account_len::<Mint>(&[ExtensionType::GroupMemberPointer,ExtensionType::TokenGroupMember,])?;// Get rent exempt balance for full space (including data extension)let member_mint_rent = client.get_minimum_balance_for_rent_exemption(member_mint_space_with_data).await?;let create_member_mint_account_instruction = create_account(&authority.pubkey(), // payer&member_mint.pubkey(), // new account (mint)member_mint_rent, // lamportsmember_mint_space as u64, // space (pointer only for account creation)&TOKEN_2022_PROGRAM_ID, // program id);let initialize_member_pointer_instruction = initialize_group_member_pointer(&TOKEN_2022_PROGRAM_ID,&member_mint.pubkey(), // mintSome(authority.pubkey()), // authoritySome(member_mint.pubkey()), // member address (points to itself))?;let initialize_member_mint_instruction = initialize_mint(&TOKEN_2022_PROGRAM_ID, // program id&member_mint.pubkey(), // mint&authority.pubkey(), // mint authoritySome(&authority.pubkey()), // freeze authority0, // decimals (NFT-style))?;// Initialize member extension (added post-mint initialization)let initialize_member_instruction = initialize_member(&TOKEN_2022_PROGRAM_ID, // program id&member_mint.pubkey(), // member&member_mint.pubkey(), // member mint&authority.pubkey(), // member mint authority&group_mint.pubkey(), // group (references the group mint)&authority.pubkey(), // group update authority);// Build member mint transactionlet member_transaction = Transaction::new_signed_with_payer(&[create_member_mint_account_instruction,initialize_member_pointer_instruction,initialize_member_mint_instruction,initialize_member_instruction,],Some(&authority.pubkey()),&[&authority, &member_mint],latest_blockhash,);// Send and confirm member transactionclient.send_and_confirm_transaction(&member_transaction).await?;println!("Group Mint: {}", group_mint.pubkey());// Fetch and deserialize group mintlet group_mint_account = client.get_account(&group_mint.pubkey()).await?;let group_mint_state = StateWithExtensions::<Mint>::unpack(&group_mint_account.data)?;let group_extension_types = group_mint_state.get_extension_types()?;println!("Extensions enabled: {:?}", group_extension_types);let group_pointer = group_mint_state.get_extension::<GroupPointer>()?;println!("\n{:#?}", group_pointer);let token_group = group_mint_state.get_extension::<TokenGroup>()?;println!("\n{:#?}", token_group);println!("\nMember Mint: {}", member_mint.pubkey());// Fetch and deserialize member mintlet member_mint_account = client.get_account(&member_mint.pubkey()).await?;let member_mint_state = StateWithExtensions::<Mint>::unpack(&member_mint_account.data)?;let member_extension_types = member_mint_state.get_extension_types()?;println!("Extensions enabled: {:?}", member_extension_types);let member_pointer = member_mint_state.get_extension::<GroupMemberPointer>()?;println!("\n{:#?}", member_pointer);let token_group_member = member_mint_state.get_extension::<TokenGroupMember>()?;println!("\n{:#?}", token_group_member);Ok(())}
Console
Click to execute the code.
Is this page helpful?