Las operaciones de tokens pasan por el programa SPL Token a través de los constructores de instrucciones. El crate del constructor debe coincidir con el ID del programa al que estás llamando: spl_token::instruction::* para el Token clásico (TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA) y spl_token_2022::instruction::* para Token-2022 (TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb). Prefiere los constructores *_checked y valida las emisiones, autoridades y decimales antes de cualquier CPI.
// transfer_checked pasa la emisión y verifica los decimales en la cadenalet ix = spl_token::instruction::transfer_checked( token_program.key, source.key, mint.key, dest.key, authority.key, &[], amount, decimals,)?;invoke(&ix, accounts)?;
use spl_token::state::Account as TokenAccount;use solana_program::program_pack::Pack;pub fn cpi_transfer(amount: u64, decimals: u8, token_program: &AccountInfo, mint: &AccountInfo, source: &AccountInfo, dest: &AccountInfo, auth: &AccountInfo, bump: u8) -> ProgramResult { // 1. El crate del constructor y la cuenta del programa deben coincidir. spl_token::check_program_account(token_program.key)?; // 2. Ambas cuentas de token deben ser propiedad de ese programa de token. if source.owner != token_program.key || dest.owner != token_program.key { return Err(ProgramError::IncorrectProgramId); } // 3. Misma emisión en ambos lados, y es la emisión para la que estamos verificando los decimales. let src = TokenAccount::unpack(&source.try_borrow_data()?)?; let dst = TokenAccount::unpack(&dest.try_borrow_data()?)?; if src.mint != dst.mint || &src.mint != mint.key || &src.owner != auth.key { return Err(ProgramError::InvalidAccountData); } let ix = spl_token::instruction::transfer_checked( token_program.key, source.key, mint.key, dest.key, auth.key, &[], amount, decimals, )?; invoke_signed( &ix, &[source.clone(), mint.clone(), dest.clone(), auth.clone(), token_program.clone()], &[&[b"auth", &[bump]]], )}
Lo que esto demuestra:
transfer_checked construye datos + metadatos y verifica los decimales en la cadena.
Las comprobaciones de ID de programa, propietario y emisión se ejecutan antes de la CPI.
La autoridad PDA usa invoke_signed.
La cantidad son unidades de token brutas (decimales).
Token-2022 es un programa separado con un crate separado. Pasar su ID de programa a un constructor spl_token::instruction::* falla en el momento de la construcción de la instrucción: esos constructores comienzan con check_program_account(token_program_id)?, que devuelve IncorrectProgramId para cualquier cosa que no sea spl_token::ID. No hay una ruta de "simplemente intercambia la cuenta del programa".
Usa spl_token_2022::instruction::transfer_checked(...) para las emisiones de Token-2022, o anchor_spl::token_interface (Anchor 0.32.1), que distribuye entre ambos programas. Token-2022 también rechaza la instrucción Transfer sin verificar en las emisiones que llevan una extensión de tarifa de transferencia, por lo que transfer_checked es la única opción portable.
ID de programa de token incorrecto - Token-2022 vs clásico. Solución: Valida la clave de la cuenta del programa y usa el crate del constructor coincidente; los constructores de spl_token rechazan el ID de Token-2022.
transfer sin verificar - Obsoleto, y Token-2022 lo rechaza en emisiones con tarifa de transferencia. Solución: Usa transfer_checked; trata transfer simple como solo legado.
Desajuste de emisión - Drena el pool equivocado. Solución: Desempaqueta y compara las claves públicas de emisión.
Confusión de decimales - Desfase de 10^n. Solución: Usa cantidades brutas de forma consistente.
Falta de comprobación de delegado - Transferencia no autorizada. Solución: Valida el campo de autoridad.
Congelar cuenta - La transferencia falla. Solución: Maneja el código de error.
Versiones de la pila: Esta página fue escrita para Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, y LiteSVM 0.6.x.
Revisado por Chris St. John·Última actualización: 16 jul 2026