Las comisiones de transferencia de Token-2022 no se pagan a un destinatario ni a una cuenta de comisiones en el momento de la transferencia; al remitente se le debita el monto completo, al destinatario se le acredita el monto menos la comisión, y la comisión se acumula en el monto retenido del mint. La tasa de comisión y el máximo se definen por época en la extensión mint TransferFeeConfig, por lo que la comisión aplicable depende de la época actual y de la configuración del mint. Para observar el monto efectivo después de la comisión de una transferencia completada, lee preTokenBalances y postTokenBalances desde el meta de getTransaction en lugar de confiar en el monto de la instrucción. Para leer el monto retenido acumulado, decodifica la extensión TransferFeeConfig desde la cuenta mint; getTokenAccountBalance en una billetera no lo mostrará. Esta guía proporciona ejemplos ejecutables de Node.js y una tabla de resultados para medir estos valores contra tu propio endpoint RPC.
Extensión mint TransferFeeConfig y dónde viven sus campos
La extensión TransferFeeConfig es una extensión mint definida por el programa Token-2022. Según la referencia de Comisiones de Transferencia de Solana (https://solana.com/docs/references/token-extensions), almacena los puntos base de la comisión de transferencia, la comisión máxima, la autoridad de retiro y el acumulador de monto retenido. Estos campos forman parte de los datos de la cuenta mint, no de los datos de la cuenta de token, por lo que cualquier cliente que quiera razonar sobre comisiones debe obtener y decodificar el mint.
La extensión se añade al diseño base del mint. La cuenta mint base contiene los campos estándar como autoridad de mint, suministro, decimales y autoridad de congelación. La extensión TransferFeeConfig sigue a los datos base y comienza con un discriminador de tipo y un prefijo de longitud. Los desplazamientos de bytes exactos dependen de la versión del programa Token-2022 y de la presencia de otras extensiones, por lo que un decodificador robusto debería analizar la lista de extensiones en lugar de asumir un desplazamiento fijo.
La autoridad de retiro es la única cuenta autorizada para retirar las comisiones retenidas acumuladas del mint. El monto retenido es un total acumulado de comisiones que se han retenido de las transferencias pero que aún no se han retirado. No es un saldo de cuenta de token y no aparece en getTokenAccountBalance para ninguna billetera.
La extensión y sus campos están definidos por Solana en la referencia de Comisiones de Transferencia, y el diseño de la cuenta de extensión mint está documentado junto con la documentación del programa Token Extensions. Los métodos de lectura utilizados a continuación — getTokenAccountBalance, getAccountInfo y getTransaction — se especifican en la referencia de la API JSON-RPC de Solana. Trátalos como la fuente de verdad; este artículo es el procedimiento operativo de lectura construido sobre ellos.
- TransferFeeConfig es una extensión mint, no una extensión de cuenta de token.
- Los campos incluyen puntos base de comisión de transferencia, comisión máxima, autoridad de retiro y monto retenido.
- El monto retenido se acumula en el mint y solo la autoridad de retiro puede retirarlo.
- La decodificación debe tener en cuenta el orden variable de las extensiones y dataSize.
Cómo una transferencia Token-2022 mueve valor y retiene comisiones
Bajo Token-2022, una instrucción de transferencia debita la cuenta de token del remitente por el monto completo de la instrucción. La cuenta de token del destinatario se acredita por el monto menos la comisión calculada. La comisión en sí no se transfiere a ninguna cuenta en ese momento; en su lugar, se añade al monto retenido del mint. Este comportamiento está documentado en la referencia de Comisiones de Transferencia de Solana y es una diferencia central con una transferencia simple de SPL Token.
Debido a que la comisión se retiene en el mint, la suma de todos los saldos de cuentas de token de un mint puede ser menor que el suministro total del mint. La diferencia es el monto retenido, que permanece en el mint hasta que la autoridad de retiro lo retire. Esto significa que una conciliación ingenua de las cuentas de token contra el suministro mostrará una discrepancia igual a las comisiones retenidas acumuladas.
El cálculo de la comisión utiliza los puntos base de la comisión de transferencia y la comisión máxima de la extensión TransferFeeConfig. Los puntos base se aplican al monto de la transferencia y el resultado se limita a la comisión máxima. El redondeo exacto y el comportamiento del límite están definidos por el programa Token-2022 y deben verificarse contra el código fuente del programa o la documentación oficial para la versión específica del programa.
- Al remitente se le debita el monto completo; al destinatario se le acredita el monto menos la comisión.
- La comisión se añade al monto retenido del mint, no se paga a una cuenta.
- Los saldos totales de las cuentas de token pueden ser menores que el suministro del mint por el monto retenido.
- La comisión es min(puntos base aplicados al monto, comisión máxima).
Las ventanas de tasa por época hacen que los cálculos de comisiones dependan del estado
La extensión TransferFeeConfig define las tasas de comisión en ventanas basadas en épocas. Según la documentación de Token-2022, la extensión almacena un conjunto de parámetros de comisión antiguo y otro nuevo, cada uno con un valor de puntos base, una comisión máxima y una época. La tasa aplicable para una transferencia depende de la época actual en relación con esos valores de época. Esto hace que el cálculo de la comisión dependa del estado: el mismo monto de transferencia puede incurrir en comisiones diferentes según la época actual y las ventanas configuradas del mint.
Un cliente que quiera calcular la comisión de una transferencia candidata debe leer la época actual desde el nodo RPC y luego seleccionar los parámetros de comisión correctos del TransferFeeConfig del mint. Si la época actual es mayor o igual que la época más nueva, se aplican los parámetros más nuevos; de lo contrario, se aplican los parámetros más antiguos. La lógica de comparación exacta está definida por el programa Token-2022 y debe confirmarse con la documentación oficial.
Debido a que los parámetros de comisión pueden cambiar en los límites de época, una comisión calculada en un momento dado puede no coincidir con una comisión observada posteriormente. Para transferencias históricas, los parámetros de comisión aplicables son los que estaban vigentes en el momento de la transferencia, lo que puede requerir leer el estado histórico del mint o confiar en los propios metadatos de la transacción.
- TransferFeeConfig almacena conjuntos de parámetros de comisión antiguos y nuevos con épocas.
- La tasa aplicable depende de la época actual en relación con las épocas almacenadas.
- Los cálculos de comisiones dependen del estado y pueden cambiar en los límites de época.
- La reconstrucción histórica de comisiones puede requerir el estado histórico del mint o los metadatos de la transacción.
Leer montos efectivos después de la comisión desde el meta de getTransaction
El monto de la instrucción en una transferencia Token-2022 es el valor antes de la comisión. Para observar lo que el destinatario recibió realmente, lee el meta de la transacción desde getTransaction. El meta contiene los arreglos preTokenBalances y postTokenBalances, que enumeran los saldos de las cuentas de token antes y después de la transacción. La diferencia entre el saldo posterior y anterior de la cuenta de token del destinatario es el monto efectivo después de la comisión.
Este enfoque es más confiable que calcular la comisión a partir del monto de la instrucción porque refleja el resultado real en la cadena, incluido cualquier redondeo, aplicación del límite o comportamiento específico del programa. También evita la necesidad de decodificar la extensión mint para cada transferencia, aunque decodificar el mint sigue siendo necesario para comprender los parámetros de comisión y el monto retenido.
Al leer getTransaction, asegúrate de que la transacción esté finalizada o al menos confirmada según tu nivel de compromiso requerido. El meta puede ser nulo para transacciones que fallaron o que aún no están disponibles. Para obtener más información sobre los niveles de compromiso, consulta Niveles de compromiso de Solana y confirmación de transacciones.
- El monto de la instrucción es antes de la comisión; los saldos del meta muestran el resultado real después de la comisión.
- preTokenBalances y postTokenBalances están indexados por índice de cuenta y mint.
- El saldo posterior menos el anterior del destinatario es el monto efectivo recibido.
- El meta puede ser nulo para transacciones fallidas o no disponibles.
Leer el monto retenido acumulado desde la cuenta mint
El monto retenido se almacena en la extensión TransferFeeConfig de la cuenta mint. Para leerlo, obtén la cuenta mint con getAccountInfo y decodifica la extensión. El monto retenido es un u64 que representa el total de comisiones retenidas pero aún no retiradas. Aumenta con cada transferencia que incurre en una comisión y disminuye cuando la autoridad de retiro retira comisiones.
getTokenAccountBalance en una billetera no mostrará el monto retenido porque no es un saldo de cuenta de token. El monto retenido pertenece al mint, no a la cuenta de token de ningún usuario. Esta es una fuente común de confusión al conciliar saldos. Para obtener más información sobre la lectura de datos de cuentas, consulta Leer cuentas de Solana: datos, renta y cuentas de token vía RPC.
Decodificar la cuenta mint requiere analizar la lista de extensiones. Los datos base del mint van seguidos de una serie de extensiones, cada una con un tipo y una longitud. La extensión TransferFeeConfig tiene un valor de tipo específico y un diseño conocido. Un decodificador robusto debe manejar dataSize inesperados y extensiones desconocidas de forma elegante.
- El monto retenido está en la extensión TransferFeeConfig del mint.
- No es visible a través de getTokenAccountBalance en una billetera.
- Aumenta con las comisiones y disminuye al retirar.
- La decodificación debe manejar el orden variable de las extensiones y dataSize.
Ejemplo ejecutable de Node.js: obtener mint, decodificar TransferFeeConfig, calcular comisión
El siguiente ejemplo de Node.js usa @solana/web3.js para obtener una cuenta mint, decodificar la extensión TransferFeeConfig, leer la época actual y calcular la comisión para un monto de transferencia candidato. Asume que el mint tiene la extensión TransferFeeConfig y que la extensión está en un desplazamiento conocido para la versión del programa en uso. En la práctica, deberías analizar la lista de extensiones para encontrar el desplazamiento correcto.
El ejemplo usa getAccountInfo para obtener el mint, getEpochInfo para leer la época actual y un decodificador simple para los campos de TransferFeeConfig. Imprime los puntos base, la comisión máxima, el monto retenido y el monto calculado para el destinatario para un monto de transferencia dado.
const { Connection, PublicKey } = require('@solana/web3.js');
const RPC_URL = process.env.RPC_URL || 'https://api.mainnet-beta.solana.com';
const MINT = new PublicKey(process.env.MINT || 'YourMintAddressHere');
const TRANSFER_AMOUNT = BigInt(process.env.TRANSFER_AMOUNT || '1000000');
async function main() {
const connection = new Connection(RPC_URL, 'confirmed');
const mintInfo = await connection.getAccountInfo(MINT);
if (!mintInfo) throw new Error('Mint account not found');
const data = mintInfo.data;
// Base mint layout: 82 bytes for SPL Token; Token-2022 base is similar.
// Extensions start after base data. This example assumes TransferFeeConfig
// is the first extension and uses a simplified offset for demonstration.
const baseLen = 82;
const extType = data.readUInt16LE(baseLen);
const extLen = data.readUInt16LE(baseLen + 2);
if (extType !== 1) throw new Error('TransferFeeConfig extension not found at expected offset');
const extData = data.slice(baseLen + 4, baseLen + 4 + extLen);
// TransferFeeConfig layout (simplified):
// withdrawAuthority (32), withheldAmount (8), olderEpoch (8), olderBps (2), olderMax (8),
// newerEpoch (8), newerBps (2), newerMax (8)
let offset = 0;
const withdrawAuthority = new PublicKey(extData.slice(offset, offset + 32)); offset += 32;
const withheldAmount = extData.readBigUInt64LE(offset); offset += 8;
const olderEpoch = extData.readBigUInt64LE(offset); offset += 8;
const olderBps = extData.readUInt16LE(offset); offset += 2;
const olderMax = extData.readBigUInt64LE(offset); offset += 8;
const newerEpoch = extData.readBigUInt64LE(offset); offset += 8;
const newerBps = extData.readUInt16LE(offset); offset += 2;
const newerMax = extData.readBigUInt64LE(offset); offset += 8;
const epochInfo = await connection.getEpochInfo();
const currentEpoch = BigInt(epochInfo.epoch);
let bps, maxFee;
if (currentEpoch >= newerEpoch) {
bps = newerBps; maxFee = newerMax;
} else {
bps = olderBps; maxFee = olderMax;
}
const fee = (TRANSFER_AMOUNT * BigInt(bps)) / 10000n;
const cappedFee = fee > maxFee ? maxFee : fee;
const recipientAmount = TRANSFER_AMOUNT - cappedFee;
console.log('Mint:', MINT.toBase58());
console.log('Withdraw authority:', withdrawAuthority.toBase58());
console.log('Withheld amount:', withheldAmount.toString());
console.log('Current epoch:', currentEpoch.toString());
console.log('Applicable bps:', bps);
console.log('Applicable max fee:', maxFee.toString());
console.log('Transfer amount:', TRANSFER_AMOUNT.toString());
console.log('Computed fee:', cappedFee.toString());
console.log('Recipient amount:', recipientAmount.toString());
}
main().catch(console.error);Ejemplo ejecutable de Node.js: leer el monto después de la comisión desde el meta de la transacción
El siguiente ejemplo obtiene una transacción por firma y extrae el monto después de la comisión del destinatario desde preTokenBalances y postTokenBalances. Usa getTransaction con codificación jsonParsed para simplificar los arreglos de saldos. Esta es la forma más directa de observar lo que un destinatario recibió realmente.
El ejemplo asume que la transacción está confirmada y que el meta está disponible. Imprime los saldos anterior y posterior para cada cuenta de token y calcula la diferencia para el destinatario. Puedes adaptarlo para filtrar por mint o propietario.
const { Connection } = require('@solana/web3.js');
const RPC_URL = process.env.RPC_URL || 'https://api.mainnet-beta.solana.com';
const SIGNATURE = process.env.SIGNATURE || 'YourTransactionSignatureHere';
async function main() {
const connection = new Connection(RPC_URL, 'confirmed');
const tx = await connection.getTransaction(SIGNATURE, {
maxSupportedTransactionVersion: 0,
commitment: 'confirmed'
});
if (!tx) throw new Error('Transaction not found');
if (!tx.meta) throw new Error('Transaction meta is null');
const pre = tx.meta.preTokenBalances || [];
const post = tx.meta.postTokenBalances || [];
console.log('Pre token balances:');
for (const b of pre) {
console.log(' accountIndex:', b.accountIndex, 'mint:', b.mint, 'amount:', b.uiTokenAmount.uiAmountString);
}
console.log('Post token balances:');
for (const b of post) {
console.log(' accountIndex:', b.accountIndex, 'mint:', b.mint, 'amount:', b.uiTokenAmount.uiAmountString);
}
// Compute difference for each account index present in both
for (const p of post) {
const preBal = pre.find(x => x.accountIndex === p.accountIndex);
if (preBal) {
const preAmt = BigInt(preBal.uiTokenAmount.amount);
const postAmt = BigInt(p.uiTokenAmount.amount);
const diff = postAmt - preAmt;
console.log('Account index', p.accountIndex, 'delta:', diff.toString());
}
}
}
main().catch(console.error);Tabla de resultados para medir contra tu propio endpoint
Usa la siguiente tabla para registrar mediciones de tu propio endpoint RPC. Completa la dirección del mint, la presencia de la extensión, los puntos base, la comisión máxima, el monto retenido actual, el monto calculado para el destinatario y el saldo posterior observado de una transacción. Esto te ayudará a verificar que tu decodificación y cálculo de comisiones coincidan con el comportamiento en la cadena.
Ejecuta el primer ejemplo de Node.js para completar las columnas relacionadas con el mint, luego ejecuta el segundo ejemplo en una transferencia conocida para completar el saldo posterior observado. Compara el monto calculado para el destinatario con el saldo posterior observado menos el saldo anterior. Las discrepancias pueden indicar un desplazamiento de extensión incorrecto, una discrepancia de época o una versión diferente del programa.
- Dirección del mint: ______________________________
- Extensión TransferFeeConfig presente (sí/no): ______________________________
- Puntos base (aplicables): ______________________________
- Comisión máxima (aplicable): ______________________________
- Monto retenido actual: ______________________________
- Monto calculado para el destinatario para la transferencia candidata: ______________________________
- Saldo posterior observado menos saldo anterior para el destinatario: ______________________________
- Notas sobre discrepancias: ______________________________
Modos de fallo y solución de problemas
Un mint sin la extensión TransferFeeConfig no tendrá comisiones de transferencia. Si intentas decodificar la extensión y encuentras un tipo o longitud inesperados, es probable que el mint no tenga la extensión. La frase 'token extensions false' en algunas herramientas significa que la extensión está ausente. En ese caso, las transferencias se comportan como transferencias estándar de SPL Token sin comisión retenida.
Una transacción cuya comisión no fue lo que predijo un porcentaje ingenuo puede haber alcanzado el límite de comisión máxima. La comisión es min(puntos base aplicados al monto, comisión máxima). Si el porcentaje calculado excede la comisión máxima, se aplica la comisión máxima. Siempre verifica la comisión máxima aplicable para la época actual.
Los errores de decodificación de datos de cuenta por un dataSize inesperado pueden ocurrir si el mint tiene extensiones adicionales o un diseño diferente. La longitud de los datos base del mint y el orden de las extensiones pueden variar. Un decodificador robusto debería analizar la lista de extensiones leyendo el tipo y la longitud de cada extensión hasta el final de los datos de la cuenta. Para obtener más información sobre datos de cuentas, consulta Leer cuentas de Solana: datos, renta y cuentas de token vía RPC.
Si getTransaction devuelve meta nulo, la transacción puede haber fallado, no estar confirmada aún o haber sido podada. Usa un nivel de compromiso adecuado y considera el acceso de archivo para transacciones históricas. Para obtener más información sobre el meta de transacciones, consulta Decodificar el meta de transacciones de Solana e instrucciones internas.
- Extensión ausente: sin comisión de transferencia; 'token extensions false' indica ausencia.
- El límite de comisión máxima puede hacer que la comisión real sea menor que un porcentaje ingenuo.
- dataSize inesperado: analiza la lista de extensiones en lugar de usar desplazamientos fijos.
- Meta nulo: verifica el compromiso, el estado de la transacción y la disponibilidad de archivo.
Limitaciones y compensaciones de las lecturas de comisiones basadas en RPC
Calcular la comisión a partir del monto de la instrucción es incorrecto porque el monto de la instrucción es el valor antes de la comisión. La comisión real está determinada por el TransferFeeConfig del mint y la época actual, y el cambio de saldo del destinatario es el monto autoritativo después de la comisión. Confiar en el monto de la instrucción puede llevar a una contabilidad incorrecta.
Obtener y decodificar los datos completos del mint por lectura tiene un costo. Cada lectura requiere una llamada RPC a getAccountInfo y posiblemente getEpochInfo. Para aplicaciones de alta frecuencia, esto puede añadir latencia y carga. Almacenar en caché los datos del mint con un TTL corto puede ayudar, pero debes invalidar en los límites de época o cuando cambie la configuración del mint.
Las transferencias históricas pueden requerir acceso de archivo porque el meta de getTransaction no siempre está disponible para transacciones antiguas en nodos no de archivo. La disponibilidad de datos históricos varía según el proveedor. Para el comportamiento específico del proveedor, consulta la documentación de tu proveedor de RPC. Para obtener más información sobre precios de RPC y niveles de servicio, consulta Precios de RPC y Servicio de API.
- El monto de la instrucción es antes de la comisión; no lo uses como el monto recibido.
- Obtener y decodificar el mint completo por lectura añade costo; almacena en caché con cuidado.
- El meta histórico puede requerir acceso de archivo; la disponibilidad varía según el proveedor.
- Los límites y la retención específicos del proveedor deben confirmarse con tu proveedor.
Próximos pasos para integrar lecturas de comisiones de Token-2022
Para integrar lecturas de comisiones de Token-2022 en tu aplicación, comienza decodificando la extensión TransferFeeConfig del mint y almacenando en caché los parámetros de comisión aplicables por época. Luego, para cada transferencia, lee el meta de la transacción para confirmar el monto real después de la comisión. Usa la tabla de resultados para validar tu implementación contra tu propio endpoint RPC.
Para una cobertura más amplia de RPC de Solana, consulta los Endpoints RPC de Solana (Asistente RPC) y el centro de aprendizaje de OnFinality. Si necesitas consultar muchos mints o cuentas de token, considera filtros de getProgramAccounts y paginación con dataSlice para reducir la transferencia de datos. Para enviar transacciones con verificaciones previas, consulta Manejo de errores de preflight en sendTransaction de Solana.
Finalmente, revisa la documentación oficial de Solana sobre Comisiones de Transferencia y el programa Token-2022 para confirmar el diseño más reciente de la extensión y la lógica de comisiones. La referencia de la API JSON-RPC de Solana para getTokenAccountBalance, getAccountInfo y getTransaction es autoritativa para los contratos de métodos utilizados aquí.
- Decodifica y almacena en caché TransferFeeConfig por época.
- Valida con el meta de la transacción y la tabla de resultados.
- Usa filtros de getProgramAccounts para lecturas masivas.
- Confirma el diseño de la extensión con la documentación oficial.