Decimais e Valores
Os tokens SPL armazenam quantidades inteiras brutas na cadeia. Decimais na casa da moeda definem como exibir valores legíveis por humanos. Erros decimais de um por um estão entre os bugs mais caros nas integrações de tokens.
Receita
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)
}
Quando fazer isso:
- Formatação de saldos de carteira na UI
- Analisar a entrada do usuário antes de criar instruções de transferência
- Validando decimais
transfer_checkedna cadeia - Comparando preços entre tokens com decimais diferentes
Exemplo de trabalho
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(())
}
O que isso demonstra:
- RPC
jsonParsedexpõetokenAmount.amount,decimalseuiAmount - A conversão baseada em string evita o arredondamento flutuante do JavaScript em grandes saldos
transfer_checkedvincula o valor aos decimais esperados nos programas
Aprofundamento
Como funciona
- Mint
decimals: u8é imutável após a criação (0-9 típico; até 255 permitidos) - A conta de token
amount: u64é sempre unidades base brutas - Quantidade da UI = bruto / 10 ^ decimais (somente exibição, não armazenado)
transfer_checkedfalha se a instrução decimais! = mint decimais
Escolhas decimais comuns
| Estilo de ativo | Decimais | 1.0 token bruto |
|---|---|---|
| Semelhante a USDC | 6 | 1_000_000 |
| Embrulhado em SOL | 9 | 1_000_000_000 |
| NFT de número inteiro | 0 | 1 |
Notas em 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)
}
Pegadinhas
- JavaScript float math -
1.1 * 1e6erros de arredondamento. Correção: conversão de string/BigInt comotoRawAmount. - Supondo 9 decimais - USDC usa 6. Correção: sempre leia decimais da conta mint.
- Ignorando transfer_checked em programas - ataques de confusão decimal em ativos incompatíveis. Correção: use
transfer_checkedcom casas decimais perfeitas. - Exibindo bruto como UI - desativado em 10^6 na UI. Correção: formato com decimais em todos os lugares.
- ** estouro de u64 em matemática fora da cadeia ** - suprimentos enormes excedem o número inteiro seguro de JS. Correção: use BigInt de ponta a ponta.
Alternativas
| Alternativa | Usar quando | Não use quando |
|---|---|---|
uiAmount do RPC | Exibição rápida | Instruções de construção (use a string amount) |
| Constantes decimais fixas | Aplicativo de ativo único | Portfólio multitoken |
Bibliotecas decimais (decimal.js) | Precisão financeira | Carteira simples com BigInt suficiente |
Perguntas frequentes
Onde os decimais são armazenados?
Somente na conta mint. As contas de token armazenam o valor bruto.
Os decimais podem mudar após a criação do mint?
Não. Imutável na inicialização do mint.
O que é valor bruto?
Número inteiro armazenado na cadeia - o único valor usado pelas instruções.
Por que o USDC mostra 6 casas decimais?
Convenção do setor que corresponde à precisão da moeda fiduciária do emissor.
O que é transfer_checked?
Transferência que inclui decimais esperados - evita certos erros entre ativos cruzados em programas.
Os decimais podem ser 0?
Sim. Comum para NFTs (fornecimento 1, quantidade 1).
Como os comandos CLI lidam com decimais?
spl-token aceita números inteiros brutos. Você multiplica os valores da UI manualmente.
Os decimais do Token-2022 são diferentes?
O mesmo campo de decimais no nível da casa da moeda e semântica de quantidade bruta.
Qual é o suprimento máximo de u64?
~1,8e19 unidades brutas - combine com decimais para um planejamento realista do limite da UI.
A IU deve ser redonda ou no chão?
Decisão do produto - sempre documente. Os pagamentos geralmente são baixos; os displays podem arredondar.
Como os oráculos repassam valores?
Frequentemente bruto ou de ponto fixo com decimais explícitos no layout da conta - leia os documentos do protocolo.
BigInt em @solana/kit?
Use BigInt para valores brutos na construção de transações do kit 7.0.0 quando os valores excederem Number.MAX_SAFE_INTEGER.
Relacionado
- Minting & Burning - quantidades brutas de mint
- Transferências e delegados - transfer_checked
- Wrapped SOL - 9 decimais
Versões Stack: Esta página foi 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 e LiteSVM 0.6.x.