Token-2022 es una nueva versión retrocompatible del programa SPL Token que soporta características adicionales en forma de extensiones. El bytecode de estas extensiones es parte del propio programa Token-2022, no se despliegan programas por separado. El bytecode activado para un token en particular está determinado por la extensión habilitada en las cuentas mint o token.
Por ejemplo, en el programa SPL Token original, agregar metadatos como el nombre de un token, el símbolo o la URL del logotipo requiere un programa externo como Metaplex. Pero en Token-2022, puedes agregar metadatos habilitando una extensión de metadatos en el token mint.
En este artículo, aprenderemos cómo funciona Token-2022 bajo el capó y qué cambios se realizaron para soportar las extensiones. Veremos un ejemplo práctico sobre cómo crear tokens de Solana con extensiones usando Token-2022.
La arquitectura de Token-2022
Primero, analizaremos cómo Token-2022 amplía el programa SPL Token original bajo el capó, cómo los diseños de cuentas siguen siendo compatibles y cómo el conjunto de instrucciones crece sin romper los programas existentes.
Diseño de superconjunto y retrocompatibilidad
Token-2022 es un reemplazo directo (drop-in replacement) para el programa SPL Token. Cada operación que podías realizar antes (acuñar, transferir, quemar, congelar, cambiar autoridades) sigue funcionando exactamente igual. Esto se debe a que Token-2022 preserva los primeros 82 bytes de la cuenta mint original de SPL y los primeros 165 bytes de una cuenta token, los cuales contienen campos como supply, decimals, owner, freeze authority y amount. A continuación se muestra el layout de la cuenta mint original de SPL:

Un diagrama que muestra el diseño de memoria de los primeros 82 bytes de una cuenta mint de SPL
El programa original (SPL) deserializa y procesa solo esos datos; Token-2022 deserializa la misma región y luego continúa leyendo más allá de ella para las extensiones.
Las extensiones son características opcionales integradas en el programa Token-2022 que puedes habilitar en una cuenta mint o una cuenta token. El siguiente diagrama muestra el layout TLV del mint de Token-2022: los 82 bytes fijos del mint original de SPL seguidos por la región TLV de tamaño variable que almacena los datos de las extensiones.

Layout de cuenta de Token-2022 y extensiones TLV
El programa SPL Token original tiene un diseño binario fijo donde el tamaño, el tipo y el valor de cada campo están predeterminados. Token-2022 utiliza el mismo diseño binario fijo en la región preservada de los primeros 82 a 165 bytes, pero más allá de eso utiliza un esquema de codificación variable Type-Length-Value (TLV) para almacenar las extensiones.
El Type-Length-Value (TLV) es un esquema de serialización de datos. Cada objeto en los datos contiene tres partes:
- Type: un identificador que especifica qué representan los datos.
- Length: el tamaño de los datos en bytes.
- Value: los bytes de los datos reales.
Debido a que cada entrada indica tanto su tipo como su longitud, un programa puede leer el tipo para entender qué está analizando, leer exactamente ese número de bytes como el valor y luego usar la longitud para saltar directamente a la siguiente entrada.
Incluso si un tipo es desconocido, el programa aún puede usar la longitud para omitirlo.
Por ejemplo, en el diagrama a continuación, la entrada con el tipo 12 tiene una longitud de 2 bytes, por lo que su campo de valor contiene dos bytes. Si el programa no reconoce el tipo 12, puede omitir esos 2 bytes y pasar directamente a la siguiente entrada, el tipo 20.

Un diagrama que ilustra Type Length Value
Token-2022 está implementado con este esquema de codificación TLV. Cada extensión está representada como una entrada TLV y en este contexto:
- El Type es el ID único de la extensión.
- El Length es el tamaño de los datos serializados de la extensión en bytes.
- El Value son los propios datos serializados de la extensión.
Esta estructura permite que múltiples extensiones se empaqueten de manera secuencial en la misma cuenta sin conflictos.

Para ilustrar cómo funciona la codificación TLV en la práctica, veamos dos extensiones: ImmutableOwner y MetadataPointer.
Layout TLV de ImmutableOwner
La extensión ImmutableOwner es una extensión de Token-2022 que evita que el propietario de una cuenta token sea cambiado después de su creación. Analizaremos el layout TLV ahora y discutiremos más sobre su uso más adelante en este artículo.
ImmutableOwner tiene las siguientes propiedades TLV:
- Type (T):
0x0a(el ID único paraImmutableOwner) - Length (L):
0x01 0x00 0x00 0x00(el número 1 codificado como un entero little-endian de 4 bytes) - Value (V):
0x01(La extensión almacena un solo byte como valor de datos, ya sea0o1)
La definición en Rust es un struct vacío:
pub struct ImmutableOwner {}
Aunque el struct no tiene campos, la codificación TLV aún reserva un byte para V para representar si el flag está habilitado (1 para habilitado, 0 para deshabilitado). Es por eso que la entrada tiene una longitud distinta de cero.
Por lo tanto, su layout TLV se vería así:

Layout TLV de MetadataPointer
La extensión MetadataPointer define dónde se encuentran los metadatos off-chain de un token y quién puede actualizarlos.
A diferencia de la extensión ImmutableOwner, MetadataPointer contiene valores: dos valores de clave pública (authority y metadata_address).
Así es como se ve el struct de Rust:
struct MetadataPointer {
authority: PubKey;
metadata_address: PubKey;
}
En TLV, el layout contendría la siguiente información:
0x1a: Type ID (T) paraMetadataPointer0x40 0x00 0x00 0x00: Length (L) = 64 bytes (little-endian)<64 bytes>: Value (V),authorityymetadata_addressserializados apuntando a la dirección pública de los metadatos.
Debido a que la V contiene las claves públicas authority y metadata_address, utilicemos claves públicas de muestra de 32 bytes para representarlas, de modo que podamos tener una mejor comprensión de cómo se verá el layout.
Supongamos que la clave pública authority de la cuenta es:
7c4YH58z6Yd1H5pa9vHqPqN8P3f9DuzGcbj2duq5Vn6a
y la clave pública metadata_address es:
9A4q8Xzj8cQ6w6sKuS27rrR2i1cC6VnV4c7pg1Zg1Vgk
En Solana, las claves públicas son valores de 32 bytes codificados en Base58. El valor (V) es la concatenación de authority y metadata_address. Cuando ambas claves públicas se decodifican de Base58 a sus bytes sin procesar y luego se escriben en forma hexadecimal, la secuencia resultante de 64 bytes será:
// authority (32 bytes)
0x62 0x21 0x73 0xa4 0x94 0x0c 0x4c 0x3c 0x29 0x7a 0x7f 0x3c 0x4f 0xc1 0x12 0x3f
0x3b 0x34 0xc6 0x51 0x3f 0x3e 0x24 0x23 0xf3 0x1c 0xaa 0x88 0x83 0x44 0xa3 0x37
// metadata_address (32 bytes)
0x79 0x30 0x0d 0x97 0x56 0x47 0xc2 0x18 0x79 0x35 0x0d 0xe6 0x18 0x9f 0x80 0xec
0xd6 0xca 0x36 0xa5 0xb1 0x77 0x5a 0xa8 0xe4 0x45 0x66 0x7b 0x85 0xf3 0x32 0xe1
Suponiendo que una cuenta de Token-2022 tenga inicializadas las extensiones ImmutableOwner y MetadataPointer, así es como se vería el layout TLV:

Al leer una cuenta, el programa puede iterar a través de la sección TLV, decodificando selectivamente solo las extensiones que reconoce. Los tipos de extensiones desconocidas se omiten utilizando sus longitudes declaradas.
Consideremos cómo manejaría el programa una extensión desconocida UnknownExtension:
- El programa lee la entrada TLV de
UnknownExtension:- T =
0x0b(un tipo desconocido) - L =
0x14 0x00 0x00 0x00(20, little-endian) - V =
0x4…0xed(20 bytes de longitud, como especifica L)
- T =
- Dado que el tipo no es reconocido, el programa no intenta interpretar el valor
- Pero aún así lee la longitud y salta hacia adelante esa cantidad de bytes (en este caso, 20)
- Luego procede a la siguiente entrada TLV, si la hay
Ahora supongamos que la extensión desconocida tuviera un valor de 64 bytes, el programa leería el valor 64 de L y luego saltaría 64 bytes hacia adelante (sobre la V) para encontrar la siguiente T. Este enfoque hace que Token-2022 sea compatible hacia adelante (forward-compatible); las futuras extensiones no romperán los programas existentes.
Compatibilidad de instrucciones de Token-2022 y nueva funcionalidad
Una instrucción de Solana es una llamada a un programa on-chain, consta de tres campos:
- un Program ID, la clave pública del programa on-chain al que se va a llamar,
- la lista de cuentas de las que el programa leerá o en las que escribirá
- los datos de la instrucción — una secuencia arbitraria de bytes cuyo formato está definido por el programa.
El SPL Token Program original tiene 25 instrucciones únicas. Token-2022 soporta todas estas instrucciones y agrega nuevas instrucciones después de la instrucción 25 para habilitar la funcionalidad de las nuevas extensiones.
En otras palabras, las instrucciones de token existentes como MintTo, Transfer o Burn en Token-2022 se comportan exactamente igual que en SPL.
Aquí hay un ejemplo del diseño de una instrucción que le dice al programa de token que acuñe 100 tokens desde una cuenta mint hacia una cuenta token de destino:

Las aplicaciones pueden adoptar Token-2022 simplemente cambiando el Program ID en sus instrucciones.
Aquí está la lista de instrucciones de Token-2022 más allá de las 25 instrucciones originales del programa Token. Las token instructions están nombradas para coincidir con las extensiones que inicializan o administran:
25: InitializeMintCloseAuthority
26: TransferFeeExtension
27: ConfidentialTransferExtension
28: DefaultAccountStateExtension
29: Reallocate
30: MemoTransferExtension
31: CreateNativeMint
32: InitializeNonTransferableMint
33: InterestBearingMintExtension
34: CpiGuardExtension
35: InitializePermanentDelegate
36: TransferHookExtension
37: ConfidentialTransferFeeExtension
38: WithdrawExcessLamports
39: MetadataPointerExtension
40: GroupPointerExtension
41: GroupMemberPointerExtension
42: ConfidentialMintBurnExtension
43: ScaledUiAmountExtension
44: PausableExtension
Patrones de implementación y cómo crear un token Token-2022
En Token-2022, todas las extensiones deben especificarse antes de inicializar la cuenta mint o token, de modo que se pueda asignar suficiente espacio para sus datos. Una vez inicializadas, no se pueden agregar extensiones adicionales.
Puedes crear tokens de Token-2022 con sus extensiones usando la CLI de spl-token. Esta calculará el tamaño requerido para la cuenta, escribirá la entrada TLV de cada extensión en la cuenta mint y, finalmente, inicializará el mint con la instrucción InitializeMint2 en segundo plano.
Así es como se vería la plantilla de la CLI:
spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb \
create-token <extension flags>
Supongamos que queremos agregar dos extensiones a un mint: una que define una tasa de interés fija para los tokens (InterestBearingConfig) y otra que permite que el mint referencie metadatos off-chain (MetadataPointer). Podemos ejecutar el siguiente comando adjuntando los flags --interest-rate y --enable-metadata. El 5 en el siguiente comando es la tasa de interés:
spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb \
create-token --interest-rate 5 --enable-metadata
Este comando crea una cuenta mint con ambas extensiones, InterestBearingConfig y MetadataPointer, habilitadas. Internamente, asigna el tamaño completo de la cuenta usando ExtensionType::try_calculate_account_len::<Mint>(&[InterestBearingConfig, MetadataPointer])?;, escribe los parámetros de la tasa de interés inmediatamente, lo cual inicializa implícitamente la extensión InterestBearingConfig, y reserva un espacio TLV para los metadatos. Ahora, tenemos que inicializar manualmente la extensión de metadatos.
La función try_calculate_account_len (de la librería token-2022) calcula el espacio total necesario basándose en las extensiones seleccionadas, y la instrucción de inicialización de cada extensión configura sus parámetros específicos antes de que la inicialización final del mint bloquee la estructura de la cuenta.
Si ejecutas el comando spl-token que mencionamos anteriormente para habilitar las extensiones, verás un resultado como este:

En esta etapa:
- La extensión de tasa de interés se ha inicializado completamente con una tasa de interés de
5. - La extensión de metadatos tiene un espacio TLV reservado, pero aún no se ha escrito ningún contenido de metadatos.
Notarás el mensaje: “To initialize metadata inside the mint, please run spl-token initialize-metadata 5bL18vT46c7SkdN37F3pb1GdxsN8kTcZCPoRcYj6cS5w <YOUR_TOKEN_NAME> <YOUR_TOKEN_SYMBOL> <YOUR_TOKEN_URI>, and sign with the mint authority.” en la respuesta.
Ejecutaremos ese comando para finalizar la inicialización de los metadatos y poblar el bloque TLV de metadatos ya asignado.
spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb \
initialize-metadata 5bL18vT46c7SkdN37F3pb1GdxsN8kTcZCPoRcYj6cS5w \
"MyToken" "MTKN" "https://example.com/mytoken.json"
El resultado se vería así:

Si hubieras omitido --enable-metadata anteriormente, este paso fallaría, porque una vez que se inicializa el mint, no se puede agregar nuevo espacio TLV. Por lo tanto, verás un error como este:

El resumen de los pasos necesarios para agregar extensiones a un mint incluye:
- Habilitar la extensión asignando espacio para la misma.
- Inicializar la extensión. Puedes inicializar múltiples extensiones a la vez.
Para extensiones como la de interest-bearing que te permite proporcionar el parámetro requerido de una vez (lo que hicimos arriba con --interest-rate 5), habilitarás e inicializarás la extensión al mismo tiempo.
Aquí hay algunas extensiones de token y los flags que puedes usar para habilitarlas en la CLI.
| Extensión | Flag de CLI |
|---|---|
| Mint Close Authority | –enable-close |
| Transfer Fees | –transfer-fee-basis-points |
| Non-Transferable | –enable-non-transferable |
| Interest-Bearing | –interest-rate |
| Permanent Delegate | –enable-permanent-delegate |
| Transfer Hook | –transfer-hook |
| Metadata | –enable-metadata |
| Metadata Pointer | –metadata-address |
| Confidential Transfers | –enable-confidential-transfers auto |
El enum ExtensionType en el código fuente de Token-2022 de Solana define todas las extensiones disponibles.
Combinando extensiones
Ya has visto cómo combinamos las extensiones Interest Bearing y Metadata cuando creamos la cuenta mint. También viste cómo habilitar e inicializar las extensiones.
Sin embargo, no todas las extensiones se pueden combinar entre sí. Por ejemplo, no puedes combinar NonTransferable y TransferHook, porque si los tokens no se pueden transferir, un transfer hook no tiene ningún trabajo significativo. La SVM y las herramientas de desarrollo aplican esta regla, por lo que intentar combinar estas dos extensiones hará que la ejecución falle.
Además, no puedes combinar las extensiones ConfidentialTransfer y TransferHook porque las transferencias confidenciales encriptan los montos y limitan la visibilidad. Dado que los transfer hooks dependen de leer el monto transferido, ambas no pueden funcionar juntas.
Aquí hay algunas otras combinaciones de extensiones que no funcionarán juntas:
- ConfidentialTransfer y TransferFeeConfig
- ConfidentialTransfer y PermanentDelegate
Resumen
Antes de pasar a la siguiente parte de este artículo, resumamos lo que hemos discutido hasta ahora:
- Tanto los primeros 165 bytes de la cuenta Token como los primeros 82 bytes de la cuenta Mint se preservan en Token-2022.
- Las extensiones se almacenan utilizando un formato de codificación TLV (Type-Length-Value).
- Token-2022 agrega 20 nuevas instrucciones a las 25 del programa SPL Token original.
- El desarrollador debe asignar suficiente espacio para todas las extensiones que desea habilitar por adelantado durante la creación de la cuenta.
- Las extensiones deben inicializarse antes de la creación del mint.
Extensiones ImmutableOwner y NonTransferable
Ahora discutiremos dos extensiones (ImmutableOwner y NonTransferable) y demostraremos cómo podemos combinarlas para construir un programa de emisión de credenciales.
La extensión ImmutableOwner
El Token Program heredado permite cambiar el propietario de una cuenta Token a otra cuenta utilizando la instrucción SetAuthority. Token-2022 te permite hacer que el propietario sea inmutable con la extensión ImmutableOwner. La extensión ImmutableOwner bloquea permanentemente la propiedad de una cuenta Token. Eso significa que, una vez que se crea una cuenta token con esta extensión, pertenecerá permanentemente a la wallet especificada.
Aquí hay un escenario en el que la extensión ImmutableOwner previene ataques de phishing que podrían ocurrir con el Token Program legacy, el cual permite cambios de propiedad después de la creación de la cuenta.
En el Token Program legacy (sin ImmutableOwner):
- Alice crea una cuenta token asociada (ATA) llamada
AliceTokenAccount, derivada de la dirección de su wallet y un mint específico. - Ella es engañada para firmar una transacción que transfiere la propiedad de esa ATA a Bob.
- Más tarde, una aplicación calcula la dirección de la ATA de Alice utilizando la función de derivación estándar. La función sigue devolviendo
AliceTokenAccountporque no verifica los cambios de propiedad. - La aplicación envía tokens a esa dirección, asumiendo que Alice es la propietaria. Pero Bob ahora la controla, por lo que Bob recibe los tokens.
- Cualquier transferencia futura a la “ATA” de Alice también irá a Bob.
Con la extensión ImmutableOwner de Token-2022 (habilitada por defecto en las ATAs):
- La transacción del paso 2 fallaría - la propiedad no se puede cambiar
- Alice retiene el control de su ATA incluso si es engañada para firmar transacciones maliciosas
- Los tokens enviados a su dirección ATA derivada siempre llegarán a ella
Así es como Token-2022 impone esta restricción con la extensión ImmutableOwner
Cuando se ejecuta una instrucción SetAuthority (la instrucción utilizada para cambiar la propiedad de la cuenta token en el programa SPL token legacy), esta llama a la función process_set_authority la cual verifica el tipo de autoridad que está presente. Si el tipo de autoridad es AuthorityType::AccountOwner, se activa la verificación de ImmutableOwner, devuelve un error y bloquea el cambio de propiedad:

Esta implementación se puede encontrar en la lógica central del programa Token-2022 en GitHub.
La extensión ImmutableOwner siempre está habilitada por defecto para las ATAs creadas con el programa Token-2022.
Cuando creas una ATA usando el programa ATA estándar con Token-2022, el programa ATA incluye automáticamente la extensión ImmutableOwner en el proceso de inicialización de la cuenta.
Esto sucede independientemente de si lo especificas explícitamente: está integrado en la lógica del programa ATA para las cuentas de Token-2022. Esto es diferente de las cuentas token creadas manualmente con la instrucción del sistema createAccount, donde necesitarías inicializar explícitamente la extensión ImmutableOwner si así lo deseas.
Este diseño asegura que el patrón de cuenta token más común (ATAs) obtenga los beneficios de seguridad de la propiedad inmutable por defecto.
La extensión NonTransferable
La extensión NonTransferable deshabilita las transferencias de tokens por completo. Aún puedes acuñar tokens en las cuentas, pero una vez recibidos, no se pueden enviar a ningún otro lugar.
Las únicas operaciones válidas en dichas cuentas son quemar tokens (para aquellos que vienen de un contexto de Ethereum, Solana trata la quema de tokens como una operación separada de la transferencia) o cerrar cuentas vacías.
Esto es útil para construir sistemas donde los tokens representan activos intransferibles. Por ejemplo, los certificados o marcadores de identidad on-chain no deberían moverse entre usuarios, ni la deuda que un usuario tiene con un protocolo. En tales casos, emitirías NFTs con la extensión NonTransferable.
Token-2022 impone esta restricción en la lógica del programa. Si intentas realizar una transferencia desde una cuenta que posee tokens intransferibles, la instrucción fallará con un error.
Puedes ver esta comprobación en processor.rs, donde el programa inspecciona la cuenta en busca de la extensión NonTransferableAccount y rechaza la transferencia con un error NonTransferable si está presente.

Los tokens intransferibles solo pueden acuñarse en cuentas con propietarios inmutables. Esto está diseñado para prevenir la transferencia indirecta de tokens a través de cambios en la propiedad de la cuenta.
Cuando se crea un mint con la extensión NonTransferable, cualquier cuenta token creada para ese mint heredará automáticamente dos extensiones: NonTransferableAccount (que evita transferencias) e ImmutableOwner (que evita cambios de propiedad). Así es como esto se impone en la lógica del programa Token-2022 y se puede encontrar en el mismo archivo que mencionamos anteriormente en GitHub.
if mint.get_extension::<NonTransferable>().is_ok()
&& destination_account.get_extension::<ImmutableOwner>().is_err()
{
return Err(TokenError::NonTransferableNeedsImmutableOwnership.into());
}
Ejemplo: uso de las extensiones ImmutableOwner y NonTransferable para construir un programa mínimo de emisión de credenciales
Esta sección cubrirá:
- cómo definir un mint que tenga tanto la extensión
ImmutableOwnercomo laNonTransferable. - cómo emitir tokens de credenciales intransferibles a los destinatarios
- y cómo la lógica del programa Token-2022 asegura que las credenciales no se puedan transferir
Descargo de responsabilidad: El programa de Anchor descrito aquí tiene fines únicamente educativos. Para producción, asegúrate de que tus programas incluyan una validación completa, comprobaciones de seguridad y se sometan a revisión.
Configuración del proyecto
Crea un nuevo proyecto de Anchor ejecutando el comando anchor init credentials. Escribiremos todo nuestro código en el archivo programs/src/lib.rs.
Configuración
Abre programs/src/Cargo.toml y actualiza las secciones [features] y [dependencies].
En la sección [features], agrega idl-build y vincúlalo a las sub-características correspondientes en anchor-lang y anchor-spl.
En la sección [dependencies], agrega anchor-lang y anchor-spl. Asegúrate de incluir el feature init-if-needed para anchor-lang — lo necesitaremos más adelante para crear cuentas token asociadas (ATAs).
...
[features]
anchor-debug = []
cpi = ["no-entrypoint"]
default = []
**idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]**
no-entrypoint = []
no-log-ix-name = []
custom-heap = []
custom-panic = []
no-idl = []
[dependencies]
**anchor-lang = {version = "0.31.0", features = ["init-if-needed"]}
anchor-spl = {version = "0.31.0"}**
Estas dependencias son las únicas que importaremos y usaremos a lo largo de este ejemplo.
// Import necessary modules from the Anchor framework and SPL token program.
use anchor_lang::prelude::*;
use anchor_spl::{
associated_token::AssociatedToken,
token_2022_extensions::non_transferable_mint_initialize,
token_interface::{
mint_to,
Mint,
TokenAccount,
TokenInterface,
},
};
Configuración del mint
Para crear nuestra cuenta mint, la inicializamos con la extensión NonTransferable. Esta extensión se aplica únicamente al propio mint. Más tarde, cuando se crea una cuenta token asociada para este mint a través del programa ATA de Token-2022, el programa ATA agrega automáticamente las extensiones ImmutableOwner y NonTransferableAccount a la nueva cuenta token, como discutimos anteriormente.
El código a continuación muestra cómo inicializaremos la cuenta mint.
- Primero inicializa la extensión
NonTransferablecon la instrucciónNonTransferableMintInitialize. - Y luego inicializa la cuenta mint en sí con las autoridades válidas utilizando la instrucción
initialize_mint2.
// Import necessary modules from the Anchor framework and SPL token program.
use anchor_lang::prelude::*;
use anchor_spl::{
associated_token::AssociatedToken,
token_2022_extensions::non_transferable_mint_initialize,
token_interface::{
mint_to,
Mint,
TokenAccount,
TokenInterface,
},
};
/// Initializes a new mint for the credentials
/// with NonTransferable extension.
pub fn initialize_credential_mint(ctx: Context<InitializeCredentialMint>) -> Result<()> {
// Initialize the NonTransferable extension.
non_transferable_mint_initialize(CpiContext::new(
ctx.accounts.token_program.to_account_info(),
anchor_spl::token_2022_extensions::NonTransferableMintInitialize {
mint: ctx.accounts.mint.to_account_info(),
token_program_id: ctx.accounts.token_program.to_account_info(),
},
))?;
// Initialize the mint itself, setting decimals to 0 and defining authorities.
anchor_spl::token_interface::initialize_mint2(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
anchor_spl::token_interface::InitializeMint2 {
mint: ctx.accounts.mint.to_account_info(),
},
),
0, // Decimals are set to 0 because credentials are whole units and cannot be fractional.
&ctx.accounts.mint.key(), // The mint authority is the program-derived address (PDA) itself.
Some(&ctx.accounts.mint.key()), // The freeze authority is also the PDA.
)?;
Ok(())
}
Inicializaremos la cuenta mint usando las cuentas definidas en el struct a continuación:
-
El struct asigna 98 bytes de espacio para el mint
- 8 bytes para el account discriminator
- 82 bytes para almacenar la información fundamental sobre el mint, como su supply total, número de decimales y quién tiene la autoridad para crear más tokens.
- Y otros 8 bytes para la extensión
NonTransferable.
En la práctica, usaremos el método
ExtensionType::try_calculate_account_len::<PodMint>(&[ExtensionType::NonTransferable])?;para calcular el tamaño dinámicamente.
He dejado algunos comentarios en el código para una mejor comprensión del resto:
/// Defines the accounts required for the `initialize_credential_mint` instruction.
#[derive(Accounts)]
pub struct InitializeCredentialMint<'info> {
// The mint account to be initialized as a Program Derived Address (PDA).
#[account(
init,
payer = payer,
// The space allocation for the account's data:
// 8 bytes: for the account discriminator, a unique identifier for the account type in Anchor.
// 82 bytes: the standard fixed size of a SPL Token Mint account.
// 8 bytes: additional space reserved for the NonTransferable extension.
space = 8 + 82 + 8,
owner = token_program.key(),
// Defines the seeds for the Program Derived Address (PDA).
seeds = [b"mint"],
bump
)]
pub mint: InterfaceAccount<'info, Mint>,
// The account paying for the transaction and rent.
#[account(mut)]
pub payer: Signer<'info>,
// System program, required for creating accounts.
pub system_program: Program<'info, System>,
// The SPL token program.
pub token_program: Interface<'info, TokenInterface>,
}
Emitiendo credenciales
Ahora que hemos creado nuestra cuenta mint, comencemos a emitir las credenciales. Acuñaremos exactamente un token (el token de credencial) y se lo enviaremos al usuario.
pub fn issue_credential(ctx: Context<IssueCredential>) -> Result<()> {
// Mint one token to the recipient's associated token account.
mint_to(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
anchor_spl::token_interface::MintTo {
mint: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.recipient_ata.to_account_info(),
authority: ctx.accounts.authority.to_account_info(),
},
),
1, // Mint exactly one token.
)?;
Ok(())
}
La función issue_credential espera el struct IssueCredential como su contexto. Al emitir una credencial, creamos la cuenta token del usuario usando el programa de cuenta token asociada (ATA) de Token-2022. Esto aplica automáticamente la extensión ImmutableOwner a la cuenta. Dado que el mint está marcado como NonTransferable, la ATA del usuario también tendrá la extensión NonTransferableAccount aplicada de forma implícita.
/// Defines the accounts required for the `issue_credential` instruction.
#[derive(Accounts)]
pub struct IssueCredential<'info> {
// The mint account, must be mutable.
#[account(
mut,
seeds = [b"mint"],
bump,
constraint = mint.mint_authority.unwrap() == authority.key() // Ensure the authority is the mint authority.
)]
pub mint: InterfaceAccount<'info, Mint>,
// The authority signing the transaction (must be the mint authority).
#[account(mut)]
pub authority: Signer<'info>,
// The recipient's associated token account, created if it doesn't exist.
#[account(
init_if_needed,
payer = authority,
associated_token::mint = mint,
associated_token::authority = recipient,
associated_token::token_program = token_program
)]
pub recipient_ata: InterfaceAccount<'info, TokenAccount>,
// The recipient of the credential.
pub recipient: Signer<'info>,
// The SPL token program.
pub token_program: Interface<'info, TokenInterface>,
// The associated token program.
pub associated_token_program: Program<'info, AssociatedToken>,
// The system program.
pub system_program: Program<'info, System>,
}
Por lo tanto, con esta implementación será imposible:
- transferir un token de credencial: la transferencia fallará con
TokenError::NonTransferable. Hemos visto este error anteriormente en el código fuente del programa Token-2022. - cambiar el propietario de la cuenta token: la cuenta es inmutable y el cambio fallará
El código fuente completo de este programa se puede encontrar en GitHub.
Conclusión
Token-2022 hace que construir en Solana sea más flexible. Hemos aprendido sobre la arquitectura de Token-2022 y cómo mantiene la retrocompatibilidad con el programa SPL token original.
Vimos cómo se utilizan las extensiones para agregar un nuevo comportamiento a los tokens. Aunque las extensiones se pueden usar juntas, hay otras combinaciones que no están permitidas.
Y finalmente, construimos un programa de emisión de credenciales que combina las extensiones NonTransferable e ImmutableOwner. Esto mostró cómo habilitar NonTransferable en un mint evita que los tokens se transfieran, imponiendo un control estricto sobre cómo se guardan las credenciales.
Este artículo es parte de una serie de tutoriales sobre Solana.