The Group and Member Extension
The Group
and Member
extensions are Mint
account extensions that introduce the ability to create groups, like collections for NFTs, that are linked with multiple assets.
Initializing the Mint Account
The Member
and Group
extension are a little different from what we're used to doing because it's composed of 2 different extensions that both go on a Mint
account:
- The
Extension
that contains all the information about the group or member. - The
Pointer Extension
that references theMint
account where theGroup
orMember
extension lives.
Usually, when used, the Extension
and the Pointer Extension
live on the same Mint
account; and we're going to do the same for this example.
The
Group
andMember
extension can't be on the same account
Let's start with some basics before diving into the code:
While the GroupPointer
and MemberPointer
extension lives in the @solana/spl-token package
, to initialize the Group
and Member
we need to use the @solana/spl-token-group
package.
So let's install the required package:
npm i @solana/spl-token-group
Additionally, the Group
and Member
extension is one of the "only" extensions that requires you to initialize the extension after having initialized the Mint
account.
This is because the metadata initialization instruction dynamically allocates the required space for the length of the group and member content.
At the same time, this means that we're going to need to initialize the Mint
account with enough lamports to be rent exempt with the Group
or Member
extension included, but allocating enough space only for the GroupPointer
or MemberPointer
extension since the initializeGroup()
and intializeMember()
instruction actually increases the space correctly.
In the code initializing the Group
looks like this:
const mint = Keypair.generate();
// Size of Mint Account with extensions
const mintLen = getMintLen([ExtensionType.GroupPointer]);
// Minimum lamports required for Mint Account
const lamports = await connection.getMinimumBalanceForRentExemption(mintLen + TYPE_SIZE + LENGTH_SIZE + TOKEN_GROUP_SIZE);
const createAccountInstruction = SystemProgram.createAccount({
fromPubkey: keypair.publicKey,
newAccountPubkey: mint.publicKey,
space: mintLen,
lamports,
programId: TOKEN_2022_PROGRAM_ID,
});
const initializeGroupPointer = createInitializeGroupPointerInstruction(
mint.publicKey,
keypair.publicKey,
mint.publicKey,
TOKEN_2022_PROGRAM_ID,
);
const initializeMintInstruction = createInitializeMintInstruction(
mint.publicKey,
6,
keypair.publicKey,
null,
TOKEN_2022_PROGRAM_ID,
);
const initializeGroupInstruction = createInitializeGroupInstruction(
{
programId: TOKEN_2022_PROGRAM_ID,
group: mint.publicKey,
mint: mint.publicKey,
mintAuthority: keypair.publicKey,
updateAuthority: keypair.publicKey,
maxSize: BigInt(100),
}
);
const transaction = new Transaction().add(
createAccountInstruction,
initializeGroupPointer,
initializeMintInstruction,
initializeGroupInstruction,
);
const signature = await sendAndConfirmTransaction(connection, transaction, [keypair, mint], {commitment: "finalized"});
console.log(`Mint created! Check out your TX here: https://explorer.solana.com/tx/${signature}?cluster=devnet`);
And after this, we can use the group that we just created to add a member to it like this:
const member = Keypair.generate();
// Size of Member Account with extensions
const memberLen = getMintLen([ExtensionType.GroupMemberPointer]);
// Minimum lamports required for Member Account
const lamports = await connection.getMinimumBalanceForRentExemption(memberLen + TYPE_SIZE + LENGTH_SIZE + TOKEN_GROUP_MEMBER_SIZE);
const createAccountInstruction = SystemProgram.createAccount({
fromPubkey: keypair.publicKey,
newAccountPubkey: member.publicKey,
space: memberLen,
lamports,
programId: TOKEN_2022_PROGRAM_ID,
});
const initializeGroupMemberPointer = createInitializeGroupMemberPointerInstruction(
member.publicKey,
keypair.publicKey,
member.publicKey,
TOKEN_2022_PROGRAM_ID,
);
const initializeMintInstruction = createInitializeMintInstruction(
member.publicKey,
6,
keypair.publicKey,
null,
TOKEN_2022_PROGRAM_ID,
);
const initializeGroupMemberInstruction = createInitializeMemberInstruction(
{
programId: TOKEN_2022_PROGRAM_ID,
group: mint.publicKey,
member: member.publicKey,
memberMint: member.publicKey,
memberMintAuthority: keypair.publicKey,
groupUpdateAuthority: keypair.publicKey,
}
);
const transaction = new Transaction().add(
createAccountInstruction,
initializeGroupMemberPointer,
initializeMintInstruction,
initializeGroupMemberInstruction,
);
const signature = await sendAndConfirmTransaction(connection, transaction, [keypair, member], {commitment: "finalized"});
console.log(`Member created! Check out your TX here: https://explorer.solana.com/tx/${signature}?cluster=devnet`);
Updating the maxSize for Groups
As you can see, when we created the group, we allocated a maxSize
field that will limit the maximum amount of Member
that we can have in that specific group.
If we change idea, and we still have the updateAuthority
of the group, we can use the updateGroupMaxSize()
instruction to shrink or increase that number like so:
const updateGroupMaxSizeInstructions = createUpdateGroupMaxSizeInstruction(
{
programId: TOKEN_2022_PROGRAM_ID,
group: mint.publicKey,
updateAuthority: keypair.publicKey,
maxSize: BigInt(100),
}
);
Updating the updateAuthority for Groups
If we want to change the UpdateAuthority
or make it immutable in order to not let anyone add any more Member
to it, we can use the updateGroupAuthority()
instruction like so:
const updateGroupAuthorityInstructions = createUpdateGroupAuthorityInstruction(
{
programId: TOKEN_2022_PROGRAM_ID,
group: mint.publicKey,
currentAuthority: keypair.publicKey,
newAuthority: null,
}
);