A Extensão Group e Member
As extensões Group e Member são extensões de conta Mint que introduzem a capacidade de criar grupos, como coleções de NFTs, que estão vinculados a múltiplos ativos.
Inicializando a Conta Mint
As extensões Member e Group são um pouco diferentes do que estamos acostumados, porque são compostas por 2 extensões diferentes que ambas vão em uma conta Mint:
A
Extensionque contém todas as informações sobre o grupo ou membro.A
Pointer Extensionque referencia a contaMintonde a extensãoGroupouMemberestá.
Normalmente, quando usadas, a Extension e a Pointer Extension ficam na mesma conta Mint; e vamos fazer o mesmo neste exemplo.
Vamos começar com alguns conceitos básicos antes de mergulhar no código:
Enquanto a extensão GroupPointer e MemberPointer vive no crate anchor-spl, para inicializar o Group e Member precisamos usar o crate spl_token_group_interface.
Então vamos instalar o pacote necessário:
cargo add spl_token_metadata_interfaceAlém disso, a extensão Group e Member é uma das "únicas" extensões que exige que você inicialize a extensão após ter inicializado a conta Mint.
Isso porque a instrução de inicialização de metadata aloca dinamicamente o espaço necessário para o comprimento do conteúdo do grupo e membro.
Ao mesmo tempo, isso significa que vamos precisar inicializar a conta Mint com lamports suficientes para ser rent exempt com a extensão Group ou Member incluída, mas alocando espaço suficiente apenas para a extensão GroupPointer ou MemberPointer, já que as instruções initializeGroup() e initializeMember() aumentam o espaço corretamente.
No código, inicializar o Group fica assim:
use anchor_lang::prelude::*;
use anchor_spl::token_2022::spl_token_2022::extension::group_pointer::GroupPointer;
use anchor_spl::token_interface::token_group_initialize, Mint, Token2022, TokenGroupInitialize;
use anchor_spl::token_2022::spl_token_2022::extension::{BaseStateWithExtensions, ExtensionType};
pub fn initialize_group(ctx: Context<InitializeGroup>) -> Result<()> {
// Adiciona 4 bytes extras para o tamanho do MetadataExtension (2 bytes para o discriminator, 2 bytes para o comprimento)
let token_group_size =
ExtensionType::try_calculate_account_len::<PodMint>(&[ExtensionType::TokenGroup])?;
let data_len = 4 + token_group_size?;
// Calcula lamports necessários para a metadata adicional
let lamports =
data_len as u64 * DEFAULT_LAMPORTS_PER_BYTE_YEAR * DEFAULT_EXEMPTION_THRESHOLD as u64;
// Transfere lamports adicionais para a conta mint
transfer(
CpiContext::new(
ctx.accounts.system_program.to_account_info(),
Transfer {
from: ctx.accounts.payer.to_account_info(),
to: ctx.accounts.mint_account.to_account_info(),
},
),
lamports,
)?;
// Inicializa a extensão do token group
token_group_initialize(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
TokenGroupInitialize {
token_program_id: ctx.accounts.token_program.to_account_info(),
group: ctx.accounts.mint_account.to_account_info(),
mint: ctx.accounts.mint_account.to_account_info(),
mint_authority: ctx.accounts.payer.to_account_info(),
},
)
Some(ctx.accounts.payer.key()), // update_authority
10, // max_size
)?;
Ok(())
}
#[derive(Accounts)]
pub struct InitializeGroup<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init,
payer = payer,
mint::decimals = 2,
mint::authority = payer,
mint::freeze_authority = mint_account,
extensions::group_pointer::authority = payer,
extensions::group_pointer::group_address = mint_account,
)]
pub mint_account: InterfaceAccount<'info, Mint>,
pub token_program: Program<'info, Token2022>,
pub system_program: Program<'info, System>,
}E após isso, podemos usar o grupo que acabamos de criar para adicionar um membro a ele assim:
use anchor_lang::prelude::*;
use anchor_spl::token_2022::spl_token_2022::extension::group_pointer::GroupPointer;
use anchor_spl::token_interface::{
self, token_member_initialize, TokenAccount, TokenMemberInitialize,
};
pub fn initialize_member(ctx: Context<InitializeMember>) -> Result<()> {
// Adiciona 4 bytes extras para o tamanho do MetadataExtension (2 bytes para o discriminator, 2 bytes para o comprimento)
let token_group_len =
ExtensionType::try_calculate_account_len::<PodMint>(&[ExtensionType::TokenGroupMember])?;
let data_len = 4 + token_group_len?;
// Calcula lamports necessários para a metadata adicional
let lamports =
data_len as u64 * DEFAULT_LAMPORTS_PER_BYTE_YEAR * DEFAULT_EXEMPTION_THRESHOLD as u64;
// Transfere lamports adicionais para a conta mint
transfer(
CpiContext::new(
ctx.accounts.system_program.to_account_info(),
Transfer {
from: ctx.accounts.payer.to_account_info(),
to: ctx.accounts.mint_account.to_account_info(),
},
),
lamports,
)?;
// Inicializa a extensão do token group
token_member_initialize(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
TokenMemberInitialize {
token_program_id: ctx.accounts.token_program.to_account_info(),
group: ctx.accounts.group.to_account_info(),
group_update_authority: ctx.accounts.payer.to_account_info(),
member: ctx.accounts.mint_account.to_account_info(),
member_mint: ctx.accounts.mint_account.to_account_info(),
member_mint_authority: ctx.accounts.payer.to_account_info(),
},
)
)?;
Ok(())
}
#[derive(Accounts)]
pub struct InitializeMember<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(mut)]
pub group: InterfaceAccount<'info, Mint>,
#[account(
init,
payer = payer,
mint::decimals = 2,
mint::authority = mint_account,
mint::freeze_authority = mint_account,
extensions::group_member_pointer::authority = payer,
extensions::group_member_pointer::member_address = mint_account,
)]
pub mint_account: InterfaceAccount<'info, Mint>,
pub token_program: Program<'info, Token2022>,
pub system_program: Program<'info, System>,
}Atualizando o maxSize para Grupos
Como você pode ver, quando criamos o grupo, alocamos um campo maxSize que limitará a quantidade máxima de Member que podemos ter naquele grupo específico.
Se mudarmos de ideia e ainda tivermos a updateAuthority do grupo, podemos usar a instrução updateGroupMaxSize() para reduzir ou aumentar esse número, assim:
const updateGroupMaxSizeInstructions = createUpdateGroupMaxSizeInstruction({
programId: TOKEN_2022_PROGRAM_ID,
group: mint.publicKey,
updateAuthority: keypair.publicKey,
maxSize: BigInt(100),
});Atualizando a updateAuthority para Grupos
Se quisermos alterar a UpdateAuthority ou torná-la imutável para que ninguém possa adicionar mais Member a ela, podemos usar a instrução updateGroupAuthority(), assim:
const updateGroupAuthorityInstructions = createUpdateGroupAuthorityInstruction({
programId: TOKEN_2022_PROGRAM_ID,
group: mint.publicKey,
currentAuthority: keypair.publicKey,
newAuthority: null,
});