Acciones tokenizadas (xStocks)
Las acciones tokenizadas de Skipo (NVDASPV, TSLASPV, AMDSPV, GLDSPV…) son activos
rebasing. La cantidad liquidada nunca se mueve; las acciones corporativas —reinversión
de dividendos, splits, splits inversos— se expresan subiendo un multiplicador:
acciones subyacentes = cantidad liquidada × multiplicador
precio por acción = tasa / multiplicador
Las dos representaciones describen el mismo dinero: cantidad × tasa da el mismo importe
en la moneda de cotización en ambos casos. El rebase afecta solo a la pata base — un
mercado xStock es <ACCIÓN>-CLP o <ACCIÓN>-USDT, así que la otra pata es fiat o una
stablecoin.
assetFormat
Los endpoints de órdenes, fills, depósitos, retiros, saldos, precio
indicativo y cotizaciones aceptan un parámetro assetFormat:
| Valor | Qué devuelve |
|---|---|
rebased (por defecto) | Importes en términos de la acción subyacente — lo que un tenedor considera "sus acciones". |
base | Importes en términos de los tokens SPV liquidados — lo que se movió en cadena y en el libro contable. |
Dónde se acepta:
| Recurso | assetFormat |
|---|---|
Órdenes y fills — GET /v2/orders, /v2/orders/{id}, /v2/orders/{id}/fills, /v2/fills | sí — por defecto rebased |
Retiros — GET /v2/withdrawals | sí — por defecto rebased |
Depósitos — GET /v2/deposits | sí — por defecto rebased |
Saldos — GET /v2/balances | sí — por defecto rebased |
Precio indicativo — GET /v2/markets/{market}/price | sí — por defecto rebased |
Cotizaciones — POST /v2/quotes | sí, en el cuerpo — por defecto rebased |
Libro contable — GET /v2/ledger | no — siempre liquidado (por qué) |
Historial de saldos — GET /v2/balances/{assetSymbol}/history | no — siempre liquidado (por qué) |
| Webhooks | no — siempre rebased (por qué) |
La lista autoritativa de parámetros de cada endpoint está en la
referencia de endpoints, que se genera desde la propia especificación OpenAPI.
Recuerda que la API rechaza los parámetros que no conoce: mandar assetFormat a un endpoint
que no lo acepta es un 400, no algo que se ignore en silencio.
GET /v2/fills?market=NVDASPV-CLP # rebased (por defecto)
GET /v2/fills?market=NVDASPV-CLP&assetFormat=base # cifras liquidadas
GET /v2/deposits?assetSymbol=COPXX # rebased (por defecto)
GET /v2/withdrawals?assetFormat=base # cifras liquidadas
GET /v2/balances # rebased (por defecto)
GET /v2/balances?assetFormat=base # cantidades SPV liquidadas
GET /v2/markets/NVDASPV-CLP/price?side=BUY # precio por acción (por defecto)
POST /v2/quotes lo lleva en el cuerpo de la petición, no en la query. No es un capricho:
assetFormat es la unidad del amount que mandas, y un valor y su unidad no deberían
viajar por caminos distintos.
{ "baseAsset": "NVDASPV", "quoteAsset": "CLP", "amountAsset": "NVDASPV",
"side": "BUY", "amount": "10", "assetFormat": "base" }
En las dos rutas de cotización —la confirmable y el precio indicativo— gobierna las dos
direcciones: cómo se interpreta el amount que envías y en qué representación vuelven
baseAmount y rate. El market no cambia en ninguna de las dos: nombra un par de
instrumentos, no una cantidad.
rebased, en todos ellosEs un cambio deliberado respecto al comportamiento anterior, que devolvía base. Un
inversor piensa en acciones, no en tokens SPV, y es lo que hacen las plataformas
comparables (el rebase_multiplier de Kraken tiene el mismo valor por defecto). Si tu
integración depende de las cifras liquidadas, pide assetFormat=base explícitamente.
El valor por defecto es el mismo en todos los recursos que lo aceptan: no hay
endpoints que sigan devolviendo base de forma implícita.
En los demás activos no cambia nada: el multiplicador es 1 y ambos modos son idénticos.
Los dos nombres: NVDASPV y NVDAX
Una acción tokenizada tiene dos códigos para el mismo activo:
| Código | Qué es | Dónde aparece |
|---|---|---|
NVDASPV | El token SPV liquidado — lo que se movió en cadena y en el libro contable | assetSymbol en la data de referencia, base.asset, el libro, el id del market |
NVDAX | La acción subyacente — lo que un tenedor considera que tiene | baseAsset en órdenes y fills, assetSymbol en saldos, y toda la UI |
La data de referencia publica los dos, así que no hace falta hardcodear el mapeo:
// GET /v2/assets/NVDASPV
{ "assetSymbol": "NVDASPV", "rebasedSymbol": "NVDAX", "assetClass": "STOCK" }
// GET /v2/markets/NVDASPV-CLP
{ "id": "NVDASPV-CLP", "baseAsset": "NVDASPV", "rebasedBaseAsset": "NVDAX", "quoteAsset": "CLP" }
En un activo que no rebasa los dos campos traen el mismo valor, así que una UI puede leer
rebasedSymbol sin ramificar. Que sean iguales no afirma que el activo no rebase: TSLASPV
está hoy en factor 1 y es un STOCK. La clase la da assetClass.
Las dos escrituras se aceptan en la entrada
Donde un endpoint pide un activo, cualquiera de las dos escrituras resuelve. NVDAX selecciona el
mismo activo que NVDASPV:
GET /v2/assets/NVDAX # devuelve el activo NVDASPV
GET /v2/markets/NVDAX-CLP # devuelve el market NVDASPV-CLP
GET /v2/fills?assetSymbol=NVDAX # los mismos fills
GET /v2/balances/NVDAX/history # la misma serie
POST /v2/quotes {"baseAsset":"NVDAX", ...} # el mismo market
Dos cosas que conviene tener claras:
La respuesta siempre usa la escritura canónica. Pedir NVDAX-CLP devuelve id: "NVDASPV-CLP".
Un id de market nombra un par de instrumentos, no una cantidad, y nunca rebasa.
La escritura no define las unidades. Solo assetFormat lo hace. Mandar amountAsset: "NVDAX"
con assetFormat: "base" cotiza tokens liquidados, porque pediste base. Un símbolo es un nombre;
la representación es ese campo.
POST /v2/withdrawals es la excepción: solo acepta el código liquidadoEs el único sitio donde la escritura rebasada se rechaza, y no es un descuido.
Ese cuerpo no lleva assetFormat, así que la representación de amount es implícita: son tokens
liquidados. Si aceptáramos NVDAX traduciendo el símbolo sin convertir el monto, retiraríamos una
cantidad distinta de la que pediste — con NFLXSPV, cuyo factor es 10, pedir 1 acción sacaría 10.
Y la trampa es fácil de pisar: GET /v2/balances responde rebasado por defecto y te entrega
assetSymbol: "NFLXX". Mandar ese valor acá es el paso natural.
Así que se rechaza con un 400 que dice las unidades y la conversión, no solo la escritura:
{
"code": "validation_error",
"detail": "This endpoint takes the settled asset code and SETTLED amounts. 'NFLXX' is the rebased ticker for 'NFLXSPV' (1 NFLXSPV = 10 NFLXX). Resend with assetSymbol 'NFLXSPV' and 'amount' converted to settled units — divide by 10.",
"extensions": { "assetSymbol": "NFLXSPV", "rebasedSymbol": "NFLXX", "multiplier": "10" }
}
Una instrucción firmada se ejecuta como fue escrita: el monto es tuyo, no nuestro para reinterpretar.
El campo multiplier
Órdenes, fills, depósitos, retiros, saldos y cotizaciones publican el factor aplicado:
{
"id": "1096473",
"baseAsset": "NVDASPV",
"baseAmount": "10.000658218334353",
"rate": "18499.8",
"multiplier": "1.0000658218334353"
}
La relación fundamental, en una línea:
importe base × multiplier = importe rebasado
Con él puedes reconstruir la otra representación sin pedir de nuevo:
- base → rebased:
baseAmount × multiplier,rate ÷ multiplier - rebased → base:
baseAmount ÷ multiplier,rate × multiplier
Esto es lo que permite conciliar un importe de la API contra un saldo en cadena.
En los payloads de webhook no hace falta ni multiplicar: todos los eventos —retiros,
depósitos, órdenes y fills— llegan con las dos representaciones ya calculadas —el nivel
superior rebasado y un bloque base con las cifras liquidadas— más el multiplier que las
relaciona. Ver Webhooks.
Y ya no hace falta multiplicar en ninguna superficie: saldos, órdenes, fills, retiros y
depósitos llegan todos con el nivel superior en la representación que pediste y un bloque base con
las cifras liquidadas. Conciliar una tenencia o una ejecución contra el libro contable o contra un
webhook no exige una segunda lectura con otros parámetros.
En una orden el bloque lleva las dos patas y el par lleno/pendiente; en un fill, las dos patas más los acumulados liquidados de su orden:
// GET /v2/orders (assetFormat=rebased, el valor por defecto)
{
"market": "NVDASPV-CLP",
"baseAsset": "NVDAX",
"baseAmount": "0.3214865",
"rate": "207290.19029004",
"multiplier": "1.000103090792305",
"assetFormat": "rebased",
"base": {
"baseAsset": "NVDASPV",
"quoteAsset": "CLP",
"baseAmount": "0.32145337",
"filledBaseAmount": "0.32145337",
"quoteAmount": "66641",
"filledQuoteAmount": "66641",
"rate": "207311.56"
}
}
base se LEE, no se deriva — y tú tampoco deberías derivarloLas fórmulas de arriba sirven para entender la relación, pero no para reconstruir el bloque base a
partir del nivel superior. Dos razones, y las dos muerden:
El rebase es asimétrico. La pata base multiplica, rate divide, la pata de cotización no se
toca. Una división uniforme corrompe la cotización.
Y el nivel superior ya viene truncado a la precisión del activo, así que dividir de vuelta
re-fabrica una cola que no es lo que liquidó. Con cifras reales: el baseAmount liquidado de esa
orden es 0.32145337, y dividir el 0.3214865 publicado de vuelta da 0.32145336 — un dígito de
valor real, perdido en silencio.
Por eso publicamos las dos: la fila sin escalar la tenemos nosotros. Lee base, no lo calcules.
{
"assetSymbol": "NVDAX",
"balance": "125.57296048",
"multiplier": "1.0009180758490996",
"assetFormat": "rebased",
"base": { "asset": "NVDASPV", "balance": "125.45778073" }
}
base.asset viene poblado en toda superficie y en cualquier assetFormat, retiros y
depósitos incluidos. Antes ahí podía venir null en una lectura rebasada, porque el código SPV ya
no viajaba en esa respuesta; ahora el ticker rebasado se resuelve de vuelta al código liquidado, así
que el dato está siempre.
Solo queda null si esa resolución falla, lo que significa que el activo no está en el registro —
no que el dato fuera inobtenible. Nunca es el ticker subyacente: un código parecido pero
equivocado es peor que un hueco evidente cuando estás conciliando contra un webhook.
Ojo con aplicar las fórmulas de arriba a una orden completa: solo valen para la pata base.
El importe de la pata de cotización (quoteAmount) no se toca, y por eso el rate se
divide — ver una orden rebasa solo una de sus dos patas.
El multiplicador es puntual en el tiempo
Es la parte que más importa y la más fácil de equivocar:
El factor que publicamos es el que estaba vigente en el momento de ese movimiento —ese fill, ese depósito, ese retiro—, leído de la propia fila. Nunca es el factor de hoy, y nunca se recalcula.
Una acción corporativa cambia el factor hacia adelante; no cambia lo que entregó una operación pasada. Aplicar el multiplicador de hoy a un fill del año pasado reescribe la historia: si un activo hace un split 10:1, todo tu historial pasado aparecería multiplicado por diez.
Consecuencia práctica: dos movimientos del mismo activo pueden traer multiplicadores
distintos, y eso es correcto. No caches "el multiplicador de NVDASPV" como si fuera un
dato de referencia: es un dato de cada fila.
Es la regla 3 de la guía de la extensión Scaled UI Amount de Solana: un importe histórico debe mostrarse con el multiplicador vigente cuando ocurrió la transacción.
Un saldo o un precio usan el factor de HOY
La regla de arriba vale para movimientos: un fill, un depósito, un retiro llevan estampado el factor que estaba vigente cuando ocurrieron, y publicarlos con otro reescribiría la historia.
Un saldo, un precio indicativo y una cotización no son historia: describen el estado de ahora mismo. Su factor es, por definición, el vigente hoy — es exactamente lo que el emisor llama el scaled balance: lo que tienes en cadena por el multiplicador actual. Aplicarles un factor histórico sería tan incorrecto como aplicarle a un fill pasado el de hoy.
En una línea: el factor de una fila lo trae la fila; el de un estado actual lo trae el reloj.
Consecuencia práctica, y no es un error: GET /v2/balances y GET /v2/fills pueden publicar
multiplicadores distintos para el mismo activo en el mismo instante. El del saldo es el de
hoy; el del fill es el del día en que se ejecutó.
multiplier: "1" no significa "no es una acción tokenizada"Un factor de exactamente 1 es simplemente un factor que todavía no se ha movido —
TSLASPV está hoy en 1. No lo uses para clasificar activos. La clase de activo es
dato de referencia y se consulta en GET /v2/assets/{assetSymbol}.
El libro contable no acepta assetFormat
GET /v2/ledger expone deliberadamente los importes liquidados, sin assetFormat.
El libro es el registro de doble entrada de lo que realmente se movió, y hoy sus asientos no
llevan el multiplicador vigente en el momento del apunte. Honrar assetFormat obligaría a
resolver el factor en vivo, que es exactamente el error de reescritura descrito arriba.
Preferimos una cifra correcta y sin escalar a una cifra escalada y potencialmente falsa.
Para ver un fill en términos de acciones, úsalo desde /v2/fills, que sí lleva el factor de
su momento.
El historial de saldos tampoco
GET /v2/balances/{assetSymbol}/history publica cantidades liquidadas y tampoco acepta
assetFormat, por la misma razón de fondo que el libro.
Una cifra rebasada necesita el factor que estaba vigente cuando esa cifra era cierta. Esta serie
se calcula desde movimientos de libro y cada punto llega solo con su fecha y su saldo: no hay factor
por punto, y no se puede recuperar. Honrar assetFormat significaría aplicar el factor de hoy a
todo punto pasado — tras un split 10:1, cada punto anterior saltaría diez veces. Es exactamente la
reescritura de historia que la regla de arriba prohíbe.
Preferimos una serie correcta sin escalar a una escalada y posiblemente falsa. Es una brecha real y no una preferencia: se cierra cuando cada punto lleve su propio factor.
La petición sí acepta las dos escrituras —NVDAX lee la serie de NVDASPV—, porque eso solo
elige qué activo se lee. Y assetSymbol en la respuesta siempre trae el código liquidado, para que
la etiqueta coincida con las unidades que estás leyendo.
La precisión de un importe rebasado
Multiplicar por el factor fabrica decimales. Un saldo liquidado de 8 decimales por un factor de 16 da un producto de 24, y esa cola es aritmética, no información.
Todo importe rebasado se trunca a la precisión con la que Skipo opera ese activo — el
amountIncrement que publica GET /v2/assets, documentado como el paso por debajo del cual un
importe se trunca. Se trunca hacia abajo, nunca se redondea hacia arriba: en un saldo publicado
nunca sobreestima lo que tienes, y en un importe que viaja a ejecutarse nunca vende más de lo que
pediste.
El multiplier que va al lado no se trunca: es el factor, y lo necesitas entero para convertir.