Decimales y Montos
Los tokens SPL almacenan montos enteros brutos en la cadena. Los decimales en la "mint" definen cómo mostrar valores legibles por humanos. Los errores de un decimal de diferencia se encuentran entre los errores más costosos en las integraciones de tokens.
Receta
const UI = 1.5;
const raw = BigInt(Math.round(UI * 10 ** decimals));
const display = Number(raw) / 10 ** decimals;pub fn to_raw(ui: f64, decimals: u8) -> Result<u64> {
let factor = 10u64.pow(decimals as u32);
let raw = (ui * factor as f64).round() as u64;
Ok(raw)
}Cuándo usar esto:
- Formatear saldos de billetera en la interfaz de usuario
- Analizar la entrada del usuario antes de construir instrucciones de transferencia
- Validar decimales de
transfer_checkeden la cadena - Comparar precios entre tokens con diferentes decimales
Ejemplo de Trabajo
import { createSolanaRpc, address } from "@solana/kit";
const rpc = createSolanaRpc("https://api.devnet.solana.com");
const mint = address("MINT_PUBKEY");
const { value } = await rpc.getAccountInfo(mint, { encoding: "jsonParsed" }).send();
const decimals = value?.data.parsed.info.decimals as number;
function toRawAmount(uiAmount: string, decimals: number): bigint {
const [whole, frac = ""] = uiAmount.split(".");
const padded = (frac + "0".repeat(decimals)).slice(0, decimals);
return BigInt(whole + padded);
}
function toUiAmount(raw: bigint, decimals: number): string {
const s = raw.toString().padStart(decimals + 1, "0");
const whole = s.slice(0, -decimals) || "0";
const frac = s.slice(-decimals).replace(/0+$/, "");
return frac ? `${whole}.${frac}` : whole;
}
const raw = toRawAmount("2.5", decimals);
console.log("raw:", raw.toString());
console.log("ui:", toUiAmount(raw, decimals));use anchor_lang::prelude::*;
use anchor_spl::token::{self, Token, TokenAccount, TransferChecked, Mint};
#[derive(Accounts)]
pub struct SafeTransfer<'info> {
pub mint: Account<'info, Mint>,
#[account(mut)]
pub from: Account<'info, TokenAccount>,
#[account(mut)]
pub to: Account<'info, TokenAccount>,
pub authority: Signer<'info>,
pub token_program: Program<'info, Token>,
}
pub fn safe_transfer(ctx: Context<SafeTransfer>, amount: u64) -> Result<()> {
let decimals = ctx.accounts.mint.decimals;
let cpi = CpiContext::new(
ctx.accounts.token_program.to_account_info(),
TransferChecked {
mint: ctx.accounts.mint.to_account_info(),
from: ctx.accounts.from.to_account_info(),
to: ctx.accounts.to.to_account_info(),
authority: ctx.accounts.authority.to_account_info(),
},
);
token::transfer_checked(cpi, amount, decimals)?;
Ok(())
}Lo que esto demuestra:
- La RPC
jsonParsedexponetokenAmount.amount,decimalsyuiAmount - La conversión basada en cadenas evita el redondeo de flotantes de JavaScript en saldos grandes
transfer_checkedvincula el monto a los decimales esperados en los programas
Análisis Profundo
Cómo Funciona
- Los
decimals: u8de la "mint" son inmutables después de la creación (0-9 típicos; hasta 255 permitidos) - La cuenta de token
amount: u64siempre son unidades base brutas - Monto de UI = bruto / 10^decimales (solo para mostrar, no se almacena)
transfer_checkedfalla si los decimales de la instrucción != decimales de la "mint"
Opciones Comunes de Decimales
| Estilo de Activo | Decimales | Token 1.0 bruto |
|---|---|---|
| Similar a USDC | 6 | 1_000_000 |
| SOL envuelto | 9 | 1_000_000_000 |
| NFT de número entero | 0 | 1 |
Notas de Rust
use anchor_lang::solana_program::program_error::ProgramError;
pub fn mul_scaled(a: u64, b: u64) -> Result<u64, ProgramError> {
a.checked_mul(b).ok_or(ProgramError::InvalidArgument)
}Trampas
- Matemáticas de flotantes de JavaScript - Errores de redondeo de
1.1 * 1e6. Solución: conversión de cadena/BigInt comotoRawAmount. - Asumir 9 decimales - USDC usa 6. Solución: siempre leer los decimales de la cuenta de "mint".
- Omitir
transfer_checkeden programas - ataques de confusión de decimales entre activos no coincidentes. Solución: usartransfer_checkedcon los decimales de la "mint". - Mostrar bruto como UI - diferencia de 10^6 en la UI. Solución: formatear con los decimales de la "mint" en todas partes.
- Desbordamiento de
u64en matemáticas fuera de la cadena - suministros enormes exceden el entero seguro de JS. Solución: usar BigInt de principio a fin.
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
uiAmount de la RPC | Visualización rápida | Construcción de instrucciones (usar cadena amount) |
| Constantes de decimales fijos | Aplicación de un solo activo | Cartera multidivisa |
Bibliotecas de decimales (decimal.js) | Precisión financiera | Billetera simple con BigInt suficiente |
Preguntas Frecuentes
¿Dónde se almacenan los decimales?
Solo en la cuenta de "mint". Las cuentas de token almacenan el monto bruto.
¿Pueden cambiar los decimales después de la creación de la "mint"?
No. Inmutable en la inicialización de la "mint".
¿Qué es el monto bruto?
Entero almacenado en la cadena, el único valor que usan las instrucciones.
¿Por qué USDC muestra 6 decimales?
Convención industrial que coincide con la precisión fiduciaria del emisor.
¿Qué es `transfer_checked`?
Transferencia que incluye los decimales esperados; previene ciertos errores entre activos en programas.
¿Pueden los decimales ser 0?
Sí. Común para NFTs (suministro 1, monto 1).
¿Cómo manejan los comandos CLI los decimales?
spl-token acepta enteros brutos. Multiplicas los valores de UI manualmente.
¿Difieren los decimales de Token-2022?
Mismo campo de decimales a nivel de "mint" y semántica de monto bruto.
¿Cuál es el suministro máximo de u64?
~1.8e19 unidades brutas; combínalo con decimales para planificar el límite realista de la UI.
¿Debería la UI redondear o truncar?
Decisión del producto; siempre documentar. Los pagos a menudo truncan; las visualizaciones pueden redondear.
¿Cómo pasan las oráculos los montos?
A menudo brutos o de punto fijo con decimales explícitos en el diseño de la cuenta; leer la documentación del protocolo.
¿BigInt en @solana/kit?
Usa BigInt para montos brutos en la construcción de transacciones de kit 7.0.0 cuando los valores excedan Number.MAX_SAFE_INTEGER.
Relacionado
- Creación y Quema - montos brutos de "mint"
- Transferencias y Delegados -
transfer_checked - SOL Envuelto - 9 decimales
Versiones de Stack: 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.