グループおよびグループメンバー拡張機能とは?
グループとは、コレクションを表すミントです。グループメンバーとは、そのコレクションに属するミントです。
1つのミントがコレクションを表し、他のミントがそれに属するアイテムを表す場合に、これらの拡張機能を使用します。
Token Extensions Programは、4つの関連する拡張機能を使用して、トークングループデータをミントに直接保存できます:
GroupPointerは、グループのデータを保存するアカウントにミントを指定します。TokenGroupは、更新権限、現在のサイズ、最大サイズを含むグループデータ自体を保存します。GroupMemberPointerは、メンバーのデータを保存するアカウントにミントを指定します。TokenGroupMemberは、メンバーのグループアドレスとメンバー番号を保存します。
GroupPointer および GroupMemberPointer
は、トークングループインターフェースを実装するプログラムが所有する任意のアカウントを参照できます。
Token Extensions Programは、TokenGroup および TokenGroupMember
ミント拡張機能を通じて、そのインターフェースを直接実装しています。
mint accountに保存されるグループとメンバーの作成方法
mint accountに保存されるグループとメンバーを作成するには:
- グループmint accountを作成し、
GroupPointerを初期化します。 InitializeMintでグループミントを初期化します。- 同じミントに
TokenGroupを初期化します。 - メンバーmint accountを作成し、
GroupMemberPointerを初期化します。 InitializeMintでメンバーミントを初期化します。- グループミントを参照するように、同じミントに
TokenGroupMemberを初期化します。
グループミントのサイズとレントを計算する
グループミントに必要なサイズとレントを計算します。
グループミントの作成と初期化
グループミントアカウントを作成し、*rsGroupPointer*を初期化し、ミントを初期化し、*rsTokenGroup*を1つのトランザクションで初期化します。
メンバーミントのサイズとレントを計算する
メンバーミントに必要なサイズとレントを計算します。
メンバーミントの作成と初期化
メンバーミントアカウントを作成し、*rsGroupMemberPointer*を初期化し、ミントを初期化し、*rsTokenGroupMember*を1つのトランザクションで初期化します。
ポインタとインストラクションの順序
_rsGroupPointer_と_rsGroupMemberPointer_は、グループまたはメンバーデータが保存されているアカウントアドレスを格納します。_rsTokenGroup_と_rsTokenGroupMember_は、実際のグループまたはメンバーデータを格納します。
*rsGroupPointerInstruction::InitializeとrsGroupMemberPointerInstruction::Initialize*は、*rsInitializeMint*の前に実行する必要があります。*rsTokenGroupInstruction::InitializeGroupとrsTokenGroupInstruction::InitializeMember*は、*rsInitializeMintの後に実行する必要があります。各ミントについて、CreateAccount、ポインタ初期化インストラクション、およびrsInitializeMint*は、同じトランザクションに含める必要があります。
ソースリファレンス
*rsGroupPointerとrsGroupMemberPointer*は、Token Extensions
Programのポインタinstructionsです。*rsTokenGroupとrsTokenGroupMember*は、Token
Extensions Programが実装するトークングループインターフェースに従います。
グループポインタとグループメンバーポインタ
| 項目 | 説明 | ソース |
|---|---|---|
GroupPointer | グループのデータを格納するアカウントの権限とアドレスを保存するミント拡張機能。 | ソース |
GroupMemberPointer | メンバーのデータを格納するアカウントの権限とアドレスを保存するミント拡張機能。 | ソース |
GroupPointerInstruction::Initialize | *rsInitializeMint*の前にグループポインタ拡張機能を初期化します。 | ソース |
GroupPointerInstruction::Update | ミントの*rsGroupPointer*拡張機能に格納されているグループアドレスを更新します。 | ソース |
GroupMemberPointerInstruction::Initialize | *rsInitializeMint*の前にグループメンバーポインタ拡張機能を初期化します。 | ソース |
GroupMemberPointerInstruction::Update | ミントの*rsGroupMemberPointer*拡張機能に格納されているメンバーアドレスを更新します。 | ソース |
process_initialize (GroupPointer) | 初期の*rsGroupPointer*権限とグループアドレスをミントに書き込みます。 | ソース |
process_update (GroupPointer) | グループポインタ権限を検証し、ミントに格納されているグループアドレスを再書き込みします。 | ソース |
process_initialize (GroupMemberPointer) | 初期の*rsGroupMemberPointer*権限とメンバーアドレスをミントに書き込みます。 | ソース |
process_update (GroupMemberPointer) | グループメンバーポインタ権限を検証し、ミントに格納されているメンバーアドレスを再書き込みします。 | ソース |
トークングループとトークングループメンバー
| 項目 | 説明 | ソース |
|---|---|---|
TokenGroup | ミント上に保存されるトークングループインターフェースの状態。更新権限、現在のサイズ、最大サイズを含みます。 | ソース |
TokenGroupMember | ミント上に保存されるトークングループメンバーインターフェースの状態。メンバーミント、グループアドレス、メンバー番号を含みます。 | ソース |
TokenGroupInstruction::InitializeGroup | すでに初期化されたミントに対して新しいグループを初期化するために、Token Extensions Programでサポートされているトークングループインターフェースのinstructions。 | ソース |
TokenGroupInstruction::UpdateGroupMaxSize | グループで許可されるメンバーの最大数を更新するために、Token Extensions Programでサポートされているトークングループインターフェースのinstructions。 | ソース |
TokenGroupInstruction::UpdateGroupAuthority | グループの更新権限をローテーションまたはクリアするために、Token Extensions Programでサポートされているトークングループインターフェースのinstructions。 | ソース |
TokenGroupInstruction::InitializeMember | すでに初期化されたグループに対して新しいメンバーを初期化するために、Token Extensions Programでサポートされているトークングループインターフェースのinstructions。 | ソース |
process_initialize_group | ミントを検証し、*rsGroupPointerが存在することを確認し、グループミント上のrsTokenGroup*状態を割り当てます。 | ソース |
process_update_group_max_size | 現在の更新権限を検証し、グループの最大サイズを更新します。 | ソース |
process_update_group_authority | 現在の更新権限を検証し、グループの更新権限をローテーションまたはクリアします。 | ソース |
process_initialize_member | メンバーミントとグループ権限を検証し、グループサイズをインクリメントし、ミント上の*rsTokenGroupMember*状態を割り当てます。 | ソース |
TypeScript
以下のKit
の例では、生成されたインストラクションを直接使用しています。@solana/web3.js、@solana/spl-token、およびトークングループヘルパーを使用したレガシーの例は、参考として含まれています。
Kit
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,fetchMint,getInitializeGroupMemberPointerInstruction,getInitializeGroupPointerInstruction,getInitializeMintInstruction,getInitializeTokenGroupInstruction,getInitializeTokenGroupMemberInstruction,getMintSize,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 groupMint = await generateKeyPairSigner();const memberMint = await generateKeyPairSigner();const groupPointerExtension = extension("GroupPointer", {authority: client.payer.address,groupAddress: groupMint.address});const groupExtension = extension("TokenGroup", {updateAuthority: client.payer.address,mint: groupMint.address,size: 0n,maxSize: 10n});const groupMintSpace = BigInt(getMintSize([groupPointerExtension, groupExtension]));const groupMintCreateSpace = BigInt(getMintSize([groupPointerExtension]));const groupMintRent = await client.rpc.getMinimumBalanceForRentExemption(groupMintSpace).send();await client.sendTransaction([getCreateAccountInstruction({payer: client.payer, // Account funding the new mint account.newAccount: groupMint, // New group mint account to create.lamports: groupMintRent, // Lamports funding the mint account rent.space: groupMintCreateSpace, // Account size in bytes for the mint plus GroupPointer.programAddress: TOKEN_2022_PROGRAM_ADDRESS // Program that owns the mint account.}),getInitializeGroupPointerInstruction({mint: groupMint.address, // Mint account that stores the GroupPointer extension.authority: client.payer.address, // Authority allowed to update the group pointer later.groupAddress: groupMint.address // Account address that stores the group data.}),getInitializeMintInstruction({mint: groupMint.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.}),getInitializeTokenGroupInstruction({group: groupMint.address, // Mint account that stores the group data.mint: groupMint.address, // Mint that the group data describes.mintAuthority: client.payer, // Signer authorizing group initialization for the mint.updateAuthority: client.payer.address, // Authority allowed to update the group later.maxSize: 10n // Maximum number of members allowed in the group.})]);const memberPointerExtension = extension("GroupMemberPointer", {authority: client.payer.address,memberAddress: memberMint.address});const memberExtension = extension("TokenGroupMember", {mint: memberMint.address,group: groupMint.address,memberNumber: 1n});const memberMintSpace = BigInt(getMintSize([memberPointerExtension, memberExtension]));const memberMintCreateSpace = BigInt(getMintSize([memberPointerExtension]));const memberMintRent = await client.rpc.getMinimumBalanceForRentExemption(memberMintSpace).send();await client.sendTransaction([getCreateAccountInstruction({payer: client.payer, // Account funding the new mint account.newAccount: memberMint, // New member mint account to create.lamports: memberMintRent, // Lamports funding the mint account rent.space: memberMintCreateSpace, // Account size in bytes for the mint plus GroupMemberPointer.programAddress: TOKEN_2022_PROGRAM_ADDRESS // Program that owns the mint account.}),getInitializeGroupMemberPointerInstruction({mint: memberMint.address, // Mint account that stores the GroupMemberPointer extension.authority: client.payer.address, // Authority allowed to update the member pointer later.memberAddress: memberMint.address // Account address that stores the member data.}),getInitializeMintInstruction({mint: memberMint.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.}),getInitializeTokenGroupMemberInstruction({member: memberMint.address, // Mint account that stores the member data.memberMint: memberMint.address, // Mint that the member data describes.memberMintAuthority: client.payer, // Signer authorizing member initialization for the mint.group: groupMint.address, // Group mint that this member belongs to.groupUpdateAuthority: client.payer // Signer matching the group's update authority.})]);const groupMintAccount = await fetchMint(client.rpc, groupMint.address);const memberMintAccount = await fetchMint(client.rpc, memberMint.address);const groupExtensions = unwrapOption(groupMintAccount.data.extensions) ?? [];const memberExtensions = unwrapOption(memberMintAccount.data.extensions) ?? [];console.log(JSON.stringify({groupMint: groupMint.address,groupPointer: groupExtensions.find((item) =>isExtension("GroupPointer", item)),group: groupExtensions.find((item) => isExtension("TokenGroup", item)),memberMint: memberMint.address,memberPointer: memberExtensions.find((item) =>isExtension("GroupMemberPointer", item)),member: memberExtensions.find((item) =>isExtension("TokenGroupMember", item))},(_, value) => (typeof value === "bigint" ? value.toString() : value),2));
Web3.js
import {Connection,Keypair,Transaction,SystemProgram,LAMPORTS_PER_SOL,sendAndConfirmTransaction} from "@solana/web3.js";import {TOKEN_2022_PROGRAM_ID,ExtensionType,getMintLen,getMint,createInitializeMintInstruction,createInitializeGroupPointerInstruction,createInitializeGroupInstruction,createInitializeGroupMemberPointerInstruction,createInitializeMemberInstruction,getGroupPointerState,getGroupMemberPointerState,getTokenGroupState,getTokenGroupMemberState} from "@solana/spl-token";const connection = new Connection("http://localhost:8899", "confirmed");const authority = Keypair.generate();const airdropSignature = await connection.requestAirdrop(authority.publicKey,5 * LAMPORTS_PER_SOL);await connection.confirmTransaction(airdropSignature, "confirmed");const groupMint = Keypair.generate();const groupPointerExtensions = [ExtensionType.GroupPointer];const spaceWithGroupPointerExtensions = getMintLen(groupPointerExtensions);const groupAndGroupPointerExtensions = [ExtensionType.GroupPointer,ExtensionType.TokenGroup];const spaceWithGroupAndGroupPointerExtensions = getMintLen(groupAndGroupPointerExtensions);const groupMintRent = await connection.getMinimumBalanceForRentExemption(spaceWithGroupAndGroupPointerExtensions);const { blockhash: latestBlockhash } = await connection.getLatestBlockhash();const createGroupMintAccountInstruction = SystemProgram.createAccount({fromPubkey: authority.publicKey, // Account funding the new mint account.newAccountPubkey: groupMint.publicKey, // New group mint account to create.lamports: groupMintRent, // Lamports funding the mint account rent.space: spaceWithGroupPointerExtensions, // Account size in bytes for the mint plus GroupPointer.programId: TOKEN_2022_PROGRAM_ID // Program that owns the mint account.});const initializeGroupPointerInstruction =createInitializeGroupPointerInstruction(groupMint.publicKey, // Mint account that stores the GroupPointer extension.authority.publicKey, // Authority allowed to update the group pointer later.groupMint.publicKey, // Account address that stores the group data.TOKEN_2022_PROGRAM_ID // Program that owns the mint account.);const initializeGroupMintInstruction = createInitializeMintInstruction(groupMint.publicKey, // Mint account to initialize.0, // Number of decimals for the token.authority.publicKey, // Authority allowed to mint new tokens.authority.publicKey, // Authority allowed to freeze token accounts.TOKEN_2022_PROGRAM_ID // Program that owns the mint account.);const initializeGroupInstruction = createInitializeGroupInstruction({programId: TOKEN_2022_PROGRAM_ID, // Token program that owns the mint.group: groupMint.publicKey, // Mint account that stores the group data.mint: groupMint.publicKey, // Mint that the group data describes.mintAuthority: authority.publicKey, // Signer authorizing group initialization for the mint.updateAuthority: authority.publicKey, // Authority allowed to update the group later.maxSize: 10n // Maximum number of members allowed in the group.});const groupTransaction = new Transaction({feePayer: authority.publicKey,recentBlockhash: latestBlockhash}).add(createGroupMintAccountInstruction,initializeGroupPointerInstruction,initializeGroupMintInstruction,initializeGroupInstruction);await sendAndConfirmTransaction(connection,groupTransaction,[authority, groupMint],{commitment: "confirmed",skipPreflight: true});const memberMint = Keypair.generate();const memberPointerExtensions = [ExtensionType.GroupMemberPointer];const spaceWithMemberPointerExtension = getMintLen(memberPointerExtensions);const memberAndMemberPointerExtensions = [ExtensionType.GroupMemberPointer,ExtensionType.TokenGroupMember];const spaceWithMemberAndMemberPointerExtensions = getMintLen(memberAndMemberPointerExtensions);const memberMintRent = await connection.getMinimumBalanceForRentExemption(spaceWithMemberAndMemberPointerExtensions);const { blockhash: memberLatestBlockhash } =await connection.getLatestBlockhash();const createMemberMintAccountInstruction = SystemProgram.createAccount({fromPubkey: authority.publicKey, // Account funding the new mint account.newAccountPubkey: memberMint.publicKey, // New member mint account to create.lamports: memberMintRent, // Lamports funding the mint account rent.space: spaceWithMemberPointerExtension, // Account size in bytes for the mint plus GroupMemberPointer.programId: TOKEN_2022_PROGRAM_ID // Program that owns the mint account.});const initializeMemberPointerInstruction =createInitializeGroupMemberPointerInstruction(memberMint.publicKey, // Mint account that stores the GroupMemberPointer extension.authority.publicKey, // Authority allowed to update the member pointer later.memberMint.publicKey, // Account address that stores the member data.TOKEN_2022_PROGRAM_ID // Program that owns the mint account.);const initializeMemberMintInstruction = createInitializeMintInstruction(memberMint.publicKey, // Mint account to initialize.0, // Number of decimals for the token.authority.publicKey, // Authority allowed to mint new tokens.authority.publicKey, // Authority allowed to freeze token accounts.TOKEN_2022_PROGRAM_ID // Program that owns the mint account.);const initializeMemberInstruction = createInitializeMemberInstruction({programId: TOKEN_2022_PROGRAM_ID, // Token program that owns the mint.member: memberMint.publicKey, // Mint account that stores the member data.memberMint: memberMint.publicKey, // Mint that the member data describes.memberMintAuthority: authority.publicKey, // Signer authorizing member initialization for the mint.group: groupMint.publicKey, // Group mint that this member belongs to.groupUpdateAuthority: authority.publicKey // Signer matching the group's update authority.});const memberTransaction = new Transaction({feePayer: authority.publicKey,recentBlockhash: memberLatestBlockhash}).add(createMemberMintAccountInstruction,initializeMemberPointerInstruction,initializeMemberMintInstruction,initializeMemberInstruction);await sendAndConfirmTransaction(connection,memberTransaction,[authority, memberMint],{commitment: "confirmed",skipPreflight: true});const groupMintAccount = await getMint(connection,groupMint.publicKey,"confirmed",TOKEN_2022_PROGRAM_ID);const memberMintAccount = await getMint(connection,memberMint.publicKey,"confirmed",TOKEN_2022_PROGRAM_ID);console.log(JSON.stringify({groupMint: groupMint.publicKey,groupPointer: getGroupPointerState(groupMintAccount),group: getTokenGroupState(groupMintAccount),memberMint: memberMint.publicKey,memberPointer: getGroupMemberPointerState(memberMintAccount),member: getTokenGroupMemberState(memberMintAccount)},(_, value) => (typeof value === "bigint" ? value.toString() : value),2));
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<()> {let client = RpcClient::new_with_commitment(String::from("http://localhost:8899"),CommitmentConfig::confirmed(),);let authority = Keypair::new();let airdrop_signature = client.request_airdrop(&authority.pubkey(), 5_000_000_000).await?;loop {let confirmed = client.confirm_transaction(&airdrop_signature).await?;if confirmed {break;}}let group_mint = Keypair::new();let group_mint_space =ExtensionType::try_calculate_account_len::<Mint>(&[ExtensionType::GroupPointer])?;let group_mint_space_with_data = ExtensionType::try_calculate_account_len::<Mint>(&[ExtensionType::GroupPointer,ExtensionType::TokenGroup,])?;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(), // Account funding the new mint account.&group_mint.pubkey(), // New group mint account to create.group_mint_rent, // Lamports funding the mint account rent.group_mint_space as u64, // Account size in bytes for the mint plus GroupPointer.&TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.);let initialize_group_pointer_instruction = initialize_group_pointer(&TOKEN_2022_PROGRAM_ID,&group_mint.pubkey(), // Mint account that stores the GroupPointer extension.Some(authority.pubkey()), // Authority allowed to update the group pointer later.Some(group_mint.pubkey()), // Account address that stores the group data.)?;let initialize_group_mint_instruction = initialize_mint(&TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.&group_mint.pubkey(), // Mint account to initialize.&authority.pubkey(), // Authority allowed to mint new tokens.Some(&authority.pubkey()), // Authority allowed to freeze token accounts.0, // Number of decimals for the token.)?;let initialize_group_instruction = initialize_group(&TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.&group_mint.pubkey(), // Mint account that stores the group data.&group_mint.pubkey(), // Mint that the group data describes.&authority.pubkey(), // Signer authorizing group initialization for the mint.Some(authority.pubkey()), // Authority allowed to update the group later.10, // Maximum number of members allowed in the group.);let 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],client.get_latest_blockhash().await?,);client.send_and_confirm_transaction(&group_transaction).await?;let member_mint = Keypair::new();let member_mint_space =ExtensionType::try_calculate_account_len::<Mint>(&[ExtensionType::GroupMemberPointer])?;let member_mint_space_with_data = ExtensionType::try_calculate_account_len::<Mint>(&[ExtensionType::GroupMemberPointer,ExtensionType::TokenGroupMember,])?;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(), // Account funding the new mint account.&member_mint.pubkey(), // New member mint account to create.member_mint_rent, // Lamports funding the mint account rent.member_mint_space as u64, // Account size in bytes for the mint plus GroupMemberPointer.&TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.);let initialize_member_pointer_instruction = initialize_group_member_pointer(&TOKEN_2022_PROGRAM_ID,&member_mint.pubkey(), // Mint account that stores the GroupMemberPointer extension.Some(authority.pubkey()), // Authority allowed to update the member pointer later.Some(member_mint.pubkey()), // Account address that stores the member data.)?;let initialize_member_mint_instruction = initialize_mint(&TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.&member_mint.pubkey(), // Mint account to initialize.&authority.pubkey(), // Authority allowed to mint new tokens.Some(&authority.pubkey()), // Authority allowed to freeze token accounts.0, // Number of decimals for the token.)?;let initialize_member_instruction = initialize_member(&TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.&member_mint.pubkey(), // Mint account that stores the member data.&member_mint.pubkey(), // Mint that the member data describes.&authority.pubkey(), // Signer authorizing member initialization for the mint.&group_mint.pubkey(), // Group mint that this member belongs to.&authority.pubkey(), // Signer matching the group's update authority.);let 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],client.get_latest_blockhash().await?,);client.send_and_confirm_transaction(&member_transaction).await?;let group_mint_account = client.get_account(&group_mint.pubkey()).await?;let group_mint_state = StateWithExtensions::<Mint>::unpack(&group_mint_account.data)?;let group_pointer = group_mint_state.get_extension::<GroupPointer>()?;let token_group = group_mint_state.get_extension::<TokenGroup>()?;let member_mint_account = client.get_account(&member_mint.pubkey()).await?;let member_mint_state = StateWithExtensions::<Mint>::unpack(&member_mint_account.data)?;let member_pointer = member_mint_state.get_extension::<GroupMemberPointer>()?;let token_group_member = member_mint_state.get_extension::<TokenGroupMember>()?;println!("\nGroup Mint: {}", group_mint.pubkey());println!("\nGroup Pointer: {:#?}", group_pointer);println!("\nToken Group: {:#?}", token_group);println!("\nMember Mint: {}", member_mint.pubkey());println!("\nGroup Member Pointer: {:#?}", member_pointer);println!("\nToken Group Member: {:#?}", token_group_member);Ok(())}
Is this page helpful?