Token Extensions
Embora o programa Token original fornecesse funcionalidades essenciais como cunhagem (minting), transferência e congelamento de tokens, as Token Extensions desbloqueiam um novo paradigma de tokens programáveis.
Este programa aprimorado mantém total compatibilidade com as operações existentes do SPL Token enquanto adiciona recursos sofisticados como transfer hooks para execução de lógica personalizada, mecanismos de taxas integrados, suporte aprimorado a metadados, cálculos com juros e controles de segurança avançados.
Compatibilidade de Extensões
As Token Extensions são projetadas para serem composáveis, permitindo que você combine múltiplas extensões para criar tokens que correspondam perfeitamente aos requisitos do seu projeto.
No entanto, certas combinações são incompatíveis devido a funcionalidades conflitantes ou contradições lógicas, como:
Non-transferable + transfer hooks / transfer fees / confidential transfer
Confidential transfer + transfer fees (até 1.18)
Confidential transfer + transfer hooks (essas transferências só podem ver as contas de origem/destino, portanto não podem atuar sobre o valor transferido)
Confidential transfer + permanent delegate
Extensão Transfer Fee
A extensão TransferFee é uma extensão de Mint que permite ao criador definir um "imposto" sobre o token que é cobrado toda vez que alguém realiza uma troca (swap).
Para garantir que o destinatário da taxa não seja bloqueado para escrita toda vez que alguém realiza uma troca, e para garantir que possamos paralelizar transações contendo uma Mint com esta extensão, a taxa é reservada na Token Account do destinatário e somente a Withdraw Authority pode sacá-la.
Por esse exato motivo, para usar a extensão TransferFee, precisamos de 2 tipos diferentes de extensões: uma que vai diretamente na conta Mint chamada TransferFeeConfig que contém todos os dados necessários para realizar uma troca, e outra que vai na conta Token chamada TransferFeeAmount que "registra" quanto token está retido pela token account.
É assim que os dados da extensão TransferFee se parecem:
/// TransferFeeConfig Extension
pub const transfer_fee_config_header: [u8; 4] = [1, 0, 108, 0];
pub struct TransferFeeConfig {
pub transfer_fee_config_authority: Pubkey,
pub withdraw_withheld_authority: Pubkey,
pub withheld_amount: u64,
pub older_transfer_fee: TransferFee,
pub newer_transfer_fee: TransferFee,
}
pub struct TransferFee {
pub epoch: u64,
pub maximum_fee: u64,
pub transfer_fee_basis_point: u16,
}Alguns pontos a observar:
A
config_authoritypode ser diferente de quem realmente pode sacar os tokens das contasToken.Temos tanto uma struct de transfer fee
olderquantonewer.
O último ponto se deve ao fato de que há um período de "cooldown" quando definimos um novo TransferFee de 2 epochs para evitar rug pulls no final de uma epoch. Isso significa que durante as primeiras 2 epochs de um novo TransferFee, o TransferFee mais antigo é o que realmente está ativo.
Adicionalmente, você pode ver que o TransferFeeConfig possui um campo withheld_amount. Isso pode parecer estranho já que acabamos de dizer que as taxas de token acumulam na Token Account, mas a realidade é que reivindicar essas taxas é um processo de 2 etapas:
Reivindicar taxas da
Token Accountpara aMint. Isso pode ser feito sem permissãoReivindicar taxas da
Mintpara a conta de destino. Esta é uma ação com permissão que somente aWithdraw Authoritypode executar.
Para esta extensão, precisamos de um processo de 2 etapas para contabilizar o caso extremo em que alguém deseja fechar uma Token Account onde ainda há taxas dentro. Como o destino dessas taxas pode ser "diferente" da Withdraw Authority, precisamos contabilizar o fato de que essas taxas precisam ser enviadas para algum lugar antes de fechar a Token Account.
E é assim que os dados da extensão TransferFeeAmount se parecem:
/// TransferFeeAmount Extension
pub const transfer_amount_config_header: [u8; 4] = [2, 0, 8, 0];
pub struct TransferFeeAmount {
pub withheld_amount: u64,
}Extensão Mint Close Authority
A extensão MintCloseAuthority é uma extensão de Mint que permite à authority fechar e recuperar o aluguel de uma conta Mint que possui um fornecimento atual de 0.
Esta extensão é útil para limpar mints não utilizadas e recuperar o SOL que foi usado para pagar a isenção de aluguel da conta. A mint só pode ser fechada quando não há tokens em circulação.
É assim que os dados da extensão MintCloseAuthority se parecem:
/// MintCloseAuthority Extension
pub const mint_close_authority_extension_header: [u8; 4] = [3, 0, 32, 0];
pub struct MintCloseAuthority {
pub close_authority: Pubkey,
}Extensão Default Account State
A extensão DefaultAccountState é uma extensão de Mint que permite que todas as Token Accounts recém-criadas para aquela mint específica sejam congeladas por padrão. A Freeze Authority da mint pode então descongelar essas contas Token para que elas se tornem utilizáveis.
Este recurso concede aos criadores de tokens a capacidade de ter maior controle sobre a distribuição de tokens, limitando quem pode deter os tokens. É particularmente útil para cenários de conformidade, requisitos de KYC/AML, ou criação de distribuições de tokens baseadas em listas de permissão onde as contas devem ser explicitamente aprovadas antes de poderem receber ou transferir tokens.
É assim que os dados da extensão DefaultAccountState se parecem:
/// DefaultAccountState Extension
pub const default_account_state_extension_header: [u8; 4] = [6, 0, 1, 0];
pub struct DefaultAccountState {
pub account_state: AccountState,
}
pub enum AccountState {
Uninitialized,
Initialized,
Frozen,
}Extensão Immutable Owner
A extensão ImmutableOwner é uma extensão de conta Token que impede qualquer mudança na propriedade da conta Token. Isso protege as contas contra acesso não autorizado e tentativas de transferência.
Esta extensão é particularmente valiosa para Associated Token Accounts (ATAs) e outras contas onde a propriedade nunca deve mudar. Ela protege contra programas maliciosos que possam tentar roubar a propriedade de contas de tokens e fornece garantias de segurança adicionais para usuários e aplicativos.
É assim que os dados da extensão ImmutableOwner se parecem:
/// ImmutableOwner Extension
pub const immutable_owner_extension_header: [u8; 4] = [7, 0, 0, 0];Extensão Memo Transfer
A MemoTranfer Extension é uma extensão de conta Token que exige que todas as transferências recebidas em uma conta de token incluam um memo, facilitando o rastreamento aprimorado de transações e a identificação de usuários.
Esta extensão é particularmente útil para exchanges, instituições reguladas e aplicativos que precisam rastrear o propósito ou a origem das transferências recebidas para fins de conformidade, contabilidade ou atendimento ao cliente. Quando habilitada, qualquer transferência para a conta falhará a menos que inclua uma instrução de memo na mesma transação.
É assim que os dados da extensão MemoTranfer se parecem:
/// MemoTranfer Extension
pub const memo_transfer_extension_header: [u8; 4] = [8, 0, 1, 0];
pub struct MemoTranfer {
pub require_incoming_transfer_memos: bool,
}Extensão Non Transferable
A extensão NonTransferable é uma extensão de conta Mint que impede que os tokens sejam transferidos entre contas, tornando-os permanentemente vinculados aos seus detentores atuais.
Esta extensão é útil para criar tokens soulbound, emblemas de conquistas, certificados ou qualquer token que represente um direito ou status não transferível. Uma vez cunhados em uma conta, esses tokens não podem ser movidos, vendidos ou transferidos para outra carteira, garantindo que permaneçam permanentemente associados ao destinatário original.
Adicionalmente, a conta Token associada a uma Mint que possui a extensão NonTransferable virá com a extensão NonTransferableAccount.
É assim que os dados das extensões NonTransferable e NonTransferableAccount se parecem:
/// NonTransferable Extension
pub const non_transferable_extension_header: [u8; 4] = [9, 0, 0, 0];
/// NonTransferableAccount Extension
pub const non_transferable_account_extension_header: [u8; 4] = [13, 0, 0, 0];Ambas as extensões são apenas flags; sua presença por si só impõe a restrição.
Extensão Interest Bearing
A extensão InterestBearing é uma extensão de conta Mint que permite aos usuários aplicar uma taxa de juros aos seus tokens e recuperar o total atualizado, incluindo juros, a qualquer momento.
É assim que os dados da extensão InterestBearing se parecem:
/// InterestBearing Extension
pub const interest_bearing_extension_header: [u8; 4] = [10, 0, 52, 0];
pub struct InterestBearing {
pub rate_authority: Pubkey,
pub initialization_timestamp: i64,
pub pre_update_average_rate: u16,
pub last_update_timestamp: i64,
pub current_rate: u16,
}Como a taxa pode ser atualizada, para garantir que os cálculos estejam corretos, existe um campo pre_update_average_rate que é usado durante o cálculo para definir o comportamento em caso de uma taxa atualizada.
Extensão Cpi Guard
A extensão CpiGuard é uma extensão de conta Token que proíbe certas ações dentro de invocações cross-program, protegendo os usuários de programas maliciosos que possam tentar manipular suas contas de tokens sem consentimento explícito.
Esta extensão é crucial para a segurança ao interagir com protocolos DeFi, DEXs, ou qualquer programa que solicite acesso à conta de tokens. Ela impede que programas realizem ações não autorizadas como alterar a propriedade, configurar delegates indesejados, ou redirecionar fundos para destinatários não intencionais durante chamadas cross-program.
Quando a extensão CpiGuard está habilitada, as seguintes operações CPI funcionam conforme descrito:
Transfer: a signing authority deve ser o owner ou delegate de conta previamente estabelecido
Burn: a signing authority deve ser o owner ou delegate de conta previamente estabelecido
Approve: proibido - nenhum delegate pode ser aprovado dentro da CPI
Close Account: o destino dos lamports deve ser o owner da conta
Set Close Authority: proibido a menos que esteja removendo
Set Owner: sempre proibido, inclusive fora da CPI
É assim que os dados da extensão CpiGuard se parecem:
/// CpiGuard Extension
pub const cpi_guard_extension_header: [u8; 4] = [11, 0, 1, 0];
pub struct CpiGuard {
pub lock_cpi: bool,
}Extensão Permanent Delegate
A extensão PermanentDelegate é uma extensão de conta Mint que permite um delegate permanente para todos os tokens da mint que é capaz de transferir ou queimar qualquer token daquela mint, de qualquer conta de token.
Esta extensão é útil para criar tokens com controle administrativo integrado, como stablecoins que precisam de capacidades de congelamento emergencial, tokens de jogos que requerem gerenciamento centralizado, ou tokens de conformidade onde um regulador precisa de supervisão permanente.
É assim que os dados da extensão PermanentDelegate se parecem:
/// PermanentDelegate Extension
pub const permanent_delegate_extension_header: [u8; 4] = [12, 0, 32, 0];
pub struct PermanentDelegate {
delegate: Pubkey,
}Extensão Transfer Hook
A extensão TransferHook é uma extensão de conta Mint que introduz a capacidade de criar contas Mint que executam lógica de instrução personalizada em cada transferência de token.
Esta extensão permite casos de uso poderosos como coleta automática de impostos, pagamentos de royalties, restrições de transferência baseadas em lógica personalizada, verificações de conformidade, ou qualquer outro comportamento programável que deva ocorrer durante as transferências. O programa hook é invocado automaticamente pelo programa de extensão sempre que uma transferência ocorre.
Para isso, os desenvolvedores devem construir um programa que implemente a Transfer Hook Interface e inicializar uma conta Mint com a extensão Transfer Hook habilitada.
Adicionalmente, a conta Token associada a uma Mint que possui a extensão TransferHook virá com a extensão TransferHookAccount.
É assim que os dados das extensões TransferHook e TransferHookAccount se parecem:
/// TransferHook Extension
pub const transfer_hook_extension_header: [u8; 4] = [14, 0, 64, 0];
pub struct TransferHook {
// A authority de atualização do transfer hook
authority: Pubkey,
// A conta do programa transfer hook
programId: Pubkey,
}
/// TransferHookAccount Extension
pub const transfer_hook_account_extension_header: [u8; 4] = [15, 0, 1, 0];
pub struct TransferHookAccount {
// Se esta conta está ou não transferindo tokens atualmente
transferring: bool,
}Extensão Metadata
A extensão Metadata é uma extensão de conta Mint que introduz a capacidade de incorporar metadados diretamente nas contas mint de forma nativa e sem a necessidade de usar outro programa.
Esta extensão é particularmente útil para NFTs, tokens e outros ativos que precisam de metadados on-chain como nomes, símbolos, imagens e atributos personalizados. Ao incorporar os metadados diretamente na conta mint, elimina-se a necessidade de programas de metadados externos e garante-se que os metadados fiquem permanentemente associados ao token.
A extensão Metadata é composta por 2 extensões diferentes que ambas vão em uma conta Mint:
A extensão
Metadataque contém todas as informações de metadados como nome, símbolo, uri e contas adicionais.A extensão
MetadataPointerque faz referência à contaMintonde a extensãoMetadatareside.
Geralmente, quando utilizadas, essas 2 extensões ficam na mesma conta Mint. Mas pode haver um caso em que os mesmos Metadados estejam sendo usados em ativos diferentes, e por esse motivo seria mais barato separar as 2 extensões e referenciar a Mint com a extensão Metadata.
Desta vez, não podemos criar um header de extensão fixo para a extensão Metadata já que ela possui dados variáveis dentro dela, e isso significa que o comprimento será diferente com base no campo.
É assim que os dados das extensões Metadata e MetadataPointer se parecem:
/// Metadata Pointer Extension
pub const metadata_pointer_extension_header: [u8; 4] = [18, 0, 64, 0]
pub struct MetadataPointer {
// Authority que pode definir o endereço dos metadados
authority: Pubkey;
// Endereço da Conta que contém os metadados
metadata_address: Pubkey;
}
/// Metadata Extension (Discriminator: 19)
pub struct TokenMetadata {
/// A authority que pode assinar para atualizar os metadados
pub update_authority: Pubkey,
/// A mint associada, usada para combater falsificação e garantir que os metadados
/// pertencem a uma mint específica
pub mint: Pubkey,
/// O nome completo do token
pub name: String,
/// O símbolo abreviado do token
pub symbol: String,
/// A URI apontando para metadados mais ricos
pub uri: String,
/// Quaisquer metadados adicionais sobre o token como pares chave-valor. O programa
/// deve evitar armazenar a mesma chave duas vezes.
pub additional_metadata: Vec<(String, String)>,
}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.
Este sistema de extensões é perfeito para criar coleções de NFTs, famílias de tokens, ou qualquer agrupamento de ativos relacionados onde você precisa rastrear a associação e impor limites de coleção. Groups podem representar coleções enquanto members representam itens individuais dentro dessas coleções.
Tanto a extensão Group quanto Member são compostas por 2 extensões diferentes que ambas vão em uma conta Mint, assim como a extensão Metadata:
A
Extensionque contém todas as informações sobre o grupo ou membro.A
Pointer Extensionque faz referência à conta Mint onde a extensãoGroupouMemberreside.
A relação entre um grupo e um membro é que um grupo pode ter múltiplos membros, mas não o contrário.
Assim como na extensão Metadata, aqui colocamos tanto a Extension quanto a Pointer geralmente na mesma conta Mint, e para criar a extensão Group e Member precisaremos usar a Token Group Interface.
É assim que os dados das extensões Group e GroupPointer se parecem:
/// GroupPointer Extension
pub const group_pointer_extension_header: [u8; 4] = [20, 0, 64, 0]
pub struct GroupPointer {
// Authority que pode definir o endereço do grupo
authority: Pubkey;
// Endereço da Conta que contém o grupo
group_address: Pubkey;
}
/// Group Extension
pub const group_extension_header: [u8; 4] = [21, 0, 80, 0]
pub struct TokenGroup {
/// A authority que pode assinar para atualizar o grupo
pub update_authority: Pubkey,
/// A mint associada, usada para combater falsificação e garantir que o grupo
/// pertence a uma mint específica
pub mint: Pubkey,
/// O número atual de membros do grupo
pub size: u64,
/// O número máximo de membros do grupo
pub max_size: u64,
}É assim que os dados das extensões Member e MemberPointer se parecem:
/// MemberPointer Extension
pub const group_pointer_extension_header: [u8; 4] = [22, 0, 64, 0]
pub struct MemberPointer {
// Authority que pode definir o endereço do membro
authority: Pubkey;
// Endereço da Conta que contém o membro
member_address: Pubkey;
}
/// Member Extension
pub const group_extension_header: [u8; 4] = [23, 0, 72, 0]
pub struct TokenGroupMember {
/// A mint associada, usada para combater falsificação e garantir que o membro
/// pertence a uma mint específica
pub mint: Pubkey,
/// A pubkey do `TokenGroup`
pub group: Pubkey,
/// O número do membro
pub member_number: u64,
}