Tema
El modelo de montos
La guía que hay que leer antes de escribir cualquier aritmética. Todo lo que sigue existe porque la alternativa es un error de factor cien o mil que ninguna validación puede detectar: los dos números son montos válidos, así que nada falla, y la primera señal es el saldo equivocado de un cliente.
Un monto es un entero, y llega como cadena
No hay decimales en ninguna parte de esta API. "100000000" no son cien millones de nada: son esa cantidad de unidades mínimas del activo al que pertenece.
unidades enteras = amountMinor / 10 ** assetDecimalsY va entre comillas. No es una preferencia de estilo:
Un monto en JSON es una cadena de dígitos, nunca un número
"amountMinor": "3168490000000000000000000", no "amountMinor": 3168490000000000000000000.
COPM lleva 18 decimales, así que tres millones de pesos son 3,17 × 10²⁴ unidades mínimas. El entero más grande que JSON representa exactamente es 9 × 10¹⁵ — nueve órdenes de magnitud menos. Un monto enviado como número JSON no falla: llega redondeado, sigue siendo un monto válido, y nadie se entera hasta que las cuentas no cuadran.
El punto flotante no toca un monto en ninguna dirección. Si tu lenguaje tiene un entero de precisión arbitraria, úsalo (BigInt en JavaScript, int en Python, BigInteger en Java, big.Int en Go); si no, un decimal de precisión arbitraria. Un 1000.5 no es algo que esta API acepte, y un float que llega como 9999.999999998 es un ticket de soporte que nadie puede cerrar.
JSON.stringify no serializa un BigInt
Lanza TypeError en vez de redondear, y lo hace al enviar, donde el tipado no lo ve venir. Convierte a cadena en un solo sitio —el interceptor por donde salen todas tus peticiones— y no en cada método que arma un cuerpo. Una puerta significa que un campo que añadas mañana no puede olvidarse.
Los decimales son del activo, y dependen del mercado
Lee assetDecimals de la respuesta
Nunca lo fijes en tu código, nunca lo deduzcas del símbolo, y no supongas que el de ayer sigue sirviendo.
El mismo símbolo lleva escalas distintas según el mercado que lo reporte:
| Activo | En KiiChain Pay | En Kiiex | La convención en cadena |
|---|---|---|---|
| USDT | 6 | 4 | 6 |
| USDC | 6 | 6 | 6 |
| COPM | 18 | 2 | 18 |
| MXNB | 6 | — | 6 |
| BRLA | 18 | — | 18 |
Los dos son ciertos de su propio mercado y ninguno es un error. Lo que sí es un error es fijar uno: una integración que asuma 2 para COPM donde el mercado reporta 18 enviará la diezmilbillonésima parte de lo que pretende, y todos los montos seguirán siendo montos válidos.
Un corredor tiene una sola escala. La que aparece en la respuesta es la del corredor, no la de un mercado en abstracto — así que léela de la cotización o del saldo que estás usando, no de una tabla como la de arriba. Esta tabla es para que entiendas por qué hay que leerla, no para copiarla.
Un ejemplo completo
Tu organización tiene 10 000 USDT y quiere saber cuánto vale en pesos.
json
{ "assetSymbol": "USDT", "assetDecimals": 6, "amountMinor": "10000000000" }10000000000 / 10^6 = 10 000 USDTPides una cotización y recibes:
json
{
"rateQuoteMinor": "3128900000000000000000",
"amountBaseMinor": "10000000000",
"amountQuoteMinor": "31289000000000000000000000"
}rateQuoteMinor está en unidades mínimas del activo quote por cada unidad entera del base. COPM lleva 18 decimales aquí, así que:
3128900000000000000000 / 10^18 = 3 128,90 COPM por USDT
31289000000000000000000000 / 10^18 = 31 289 000 COPMVeintiséis dígitos. Es exactamente por esto que son cadenas.
No recalcules amountQuoteMinor por tu cuenta para compararlo. Usa el número de la respuesta.
El redondeo ya está decidido, y no es simétrico
La plataforma trunca a la baja lo que recibes y al alza lo que pagas. Es deliberado: el residuo es de la casa, y una regla de redondeo que a veces favoreciera al cliente haría que los libros dejaran de cuadrar de una forma que después nadie podría explicar.
La consecuencia práctica: las cifras de una cotización son las cifras. Rederivarlas a partir de la tasa te dará de vez en cuando un número a una unidad mínima de distancia, y el equivocado será el tuyo.
Al mostrarlo, dos decimales
Que un monto tenga 18 decimales no significa que se lea con 18. 3128.903926255989307615 no es una cifra que alguien pueda comparar con otra de un vistazo.
Dos decimales, o ninguno cuando no hay fracción. Y córtalos, no los redondees: lo que muestras junto a vas a recibir no puede ser mayor que lo que se va a recibir, porque la plataforma ya truncó a la baja al calcularlo.
La única excepción es un monto demasiado pequeño para mostrarse. Una unidad mínima de un activo de 6 decimales no es cero, y 0,00 diría que sí; ahí extiende la fracción hasta su primer dígito significativo.
Una tasa no es un precio reutilizable
Un rateQuoteMinor pertenece a la cotización en la que vino. Refleja un momento, un tamaño y las condiciones de tu organización. Guardarla y aplicarla a otro monto produce un número que la plataforma no va a honrar — y, peor, uno que tus usuarios ya vieron.
Si necesitas mostrar una tasa en vivo, pide una cotización. Son gratis y no comprometen a nada.
Lista de verificación
- [ ] Ningún
floatnidoubletoca un monto, en ningún sitio - [ ] Todo monto sale y entra como cadena de dígitos, y la conversión ocurre en un solo sitio
- [ ]
assetDecimalsviene de la API, nunca de una constante - [ ] Muestras
amountMinor / 10 ** assetDecimalscon dos decimales, y envías la inversa completa - [ ] Nunca rederivas un monto que la API ya te dio
- [ ] Tus pruebas incluyen un monto en COPM de 25 dígitos, que es el que rompe