Tokenized equities (xStocks)
Skipo's tokenized equities (NVDASPV, TSLASPV, AMDSPV, GLDSPV…) are rebasing
assets. The settled quantity never moves; corporate actions — dividend reinvestment, splits,
reverse splits — are expressed by raising a multiplier:
underlying shares = settled quantity × multiplier
price per share = rate / multiplier
Both representations describe the same money: quantity × rate yields the same quote-currency
amount either way. The rebase applies to the base leg only — an xStock market is
<STOCK>-CLP or <STOCK>-USDT, so the other leg is fiat or a stablecoin.
assetFormat
The order, fill, deposit, withdrawal, balance, indicative price and
quote endpoints accept an assetFormat parameter:
| Value | What you get |
|---|---|
rebased (default) | Amounts in terms of the underlying equity — what a holder thinks of as "their shares". |
base | Amounts in terms of the settled SPV tokens — what moved on chain and in the ledger. |
Where it is accepted:
| Resource | assetFormat |
|---|---|
Orders and fills — GET /v2/orders, /v2/orders/{id}, /v2/orders/{id}/fills, /v2/fills | yes — defaults to rebased |
Withdrawals — GET /v2/withdrawals | yes — defaults to rebased |
Deposits — GET /v2/deposits | yes — defaults to rebased |
Balances — GET /v2/balances | yes — defaults to rebased |
Indicative price — GET /v2/markets/{market}/price | yes — defaults to rebased |
Quotes — POST /v2/quotes | yes, in the body — defaults to rebased |
Ledger — GET /v2/ledger | no — always settled (why) |
Balance history — GET /v2/balances/{assetSymbol}/history | no — always settled (why) |
| Webhooks | no — always rebased (why) |
The authoritative parameter list for each endpoint is in the
endpoint reference, which is generated from the OpenAPI specification itself. Remember
that the API rejects parameters it does not know: sending assetFormat to an endpoint that
does not accept it is a 400, not something silently ignored.
GET /v2/fills?market=NVDASPV-CLP # rebased (default)
GET /v2/fills?market=NVDASPV-CLP&assetFormat=base # settled figures
GET /v2/deposits?assetSymbol=COPXX # rebased (default)
GET /v2/withdrawals?assetFormat=base # settled figures
GET /v2/balances # rebased (default)
GET /v2/balances?assetFormat=base # settled SPV quantities
GET /v2/markets/NVDASPV-CLP/price?side=BUY # price per share (default)
POST /v2/quotes takes it in the request body, not the query string. That is not arbitrary:
assetFormat is the unit of the amount you send, and a value and its unit should not
travel by different routes.
{ "baseAsset": "NVDASPV", "quoteAsset": "CLP", "amountAsset": "NVDASPV",
"side": "BUY", "amount": "10", "assetFormat": "base" }
On both quoting routes — the confirmable one and the indicative price — it governs both
directions: how the amount you send is read, and which representation baseAmount and
rate come back in. market changes in neither: it names an instrument pair, not a quantity.
rebased, on all of themThis is a deliberate change from the previous behaviour, which returned base. A holder
thinks in shares, not SPV tokens, and it is what comparable venues do (Kraken's
rebase_multiplier defaults the same way). If your integration depends on the settled
figures, ask for assetFormat=base explicitly.
The default is the same across every resource that accepts it: there are no endpoints
that still return base implicitly.
For every other asset nothing changes: the multiplier is 1 and both modes are identical.
The two names: NVDASPV and NVDAX
A tokenized equity has two codes for the same asset:
| Code | What it is | Where it appears |
|---|---|---|
NVDASPV | The settled SPV token — what moved on chain and in the ledger | assetSymbol in reference data, base.asset, the ledger, the market id |
NVDAX | The underlying share — what a holder thinks they own | baseAsset on orders and fills, assetSymbol on balances, and all UI |
Reference data publishes both, so there is no mapping to hardcode:
// GET /v2/assets/NVDASPV
{ "assetSymbol": "NVDASPV", "rebasedSymbol": "NVDAX", "assetClass": "STOCK" }
// GET /v2/markets/NVDASPV-CLP
{ "id": "NVDASPV-CLP", "baseAsset": "NVDASPV", "rebasedBaseAsset": "NVDAX", "quoteAsset": "CLP" }
For an asset that does not rebase both fields carry the same value, so a UI can read
rebasedSymbol unconditionally. Them being equal is not a claim that the asset does not rebase:
TSLASPV sits at a factor of 1 today and is a STOCK. Class comes from assetClass.
Either spelling is accepted on input
Wherever an endpoint takes an asset, either spelling resolves. NVDAX selects the same asset as
NVDASPV:
GET /v2/assets/NVDAX # returns the NVDASPV asset
GET /v2/markets/NVDAX-CLP # returns the NVDASPV-CLP market
GET /v2/fills?assetSymbol=NVDAX # the same fills
GET /v2/balances/NVDAX/history # the same series
POST /v2/quotes {"baseAsset":"NVDAX", ...} # the same market
Two things to be clear about:
The response always uses the canonical spelling. Asking for NVDAX-CLP returns
id: "NVDASPV-CLP". A market id names an instrument pair, not a quantity, and never rebases.
The spelling does not set the units. Only assetFormat does. Sending amountAsset: "NVDAX"
with assetFormat: "base" prices settled tokens, because you asked for base. A symbol is a name;
the representation is that field.
POST /v2/withdrawals is the exception: settled code onlyIt is the one place the rebased spelling is rejected, and that is deliberate.
That body carries no assetFormat, so the representation of amount is implicit: settled
tokens. If we accepted NVDAX by translating the symbol without converting the amount, we would
move a different quantity than you asked for — with NFLXSPV, whose factor is 10, asking to
withdraw 1 share would take out 10.
And the trap is easy to fall into: GET /v2/balances answers rebased by default and hands you
assetSymbol: "NFLXX". Sending that value here is the natural next step.
So it is rejected with a 400 that states the units and the conversion, not just the spelling:
{
"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" }
}
A signed instruction executes as written: the amount is yours, not ours to reinterpret.
The multiplier field
Orders, fills, deposits, withdrawals, balances and quotes publish the factor that was applied:
{
"id": "1096473",
"baseAsset": "NVDASPV",
"baseAmount": "10.000658218334353",
"rate": "18499.8",
"multiplier": "1.0000658218334353"
}
The fundamental relationship, in one line:
base amount × multiplier = rebased amount
With it you can reconstruct the other representation without another request:
- base → rebased:
baseAmount × multiplier,rate ÷ multiplier - rebased → base:
baseAmount ÷ multiplier,rate × multiplier
This is what lets you reconcile an API amount against an on-chain balance.
In webhook payloads you do not even need to multiply: every event — withdrawals, deposits,
orders and fills — arrives with both representations already computed — a rebased top level
and a base block with the settled figures — plus the multiplier that relates them. See
Webhooks.
Careful applying the formulas above to a whole order: they hold for the base leg only. The
And no surface needs you to multiply any more: balances, orders, fills, withdrawals and deposits all
arrive with the top level in the representation you asked for and a base block holding the settled
figures. Reconciling a holding or an execution against the ledger or against a webhook never needs a
second read with different parameters.
On an order the block carries both legs and the filled/ordered pair; on a fill, both legs plus its order's settled cumulative totals:
// GET /v2/orders (assetFormat=rebased, the default)
{
"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 block is READ, not derived — and you should not derive it eitherThe formulas above are there to explain the relationship, not to reconstruct the base block from
the top level. Two reasons, and both bite:
The rebase is asymmetric. The base leg multiplies, rate divides, the quote leg is untouched. A
uniform division corrupts the quote leg.
And the top level is already truncated to the asset's precision, so dividing back re-manufactures
a tail that is not what settled. With real figures: that order's settled baseAmount is
0.32145337, and dividing the published 0.3214865 back gives 0.32145336 — one digit of real
value, lost silently.
That is why we publish both: we are the ones holding the unscaled row. Read base; do not compute
it.
{
"assetSymbol": "NVDAX",
"balance": "125.57296048",
"multiplier": "1.0009180758490996",
"assetFormat": "rebased",
"base": { "asset": "NVDASPV", "balance": "125.45778073" }
}
base.asset is populated on every surface and in either assetFormat, withdrawals and
deposits included. It used to come back null there on a rebased read, because the SPV code was no
longer on that response; the rebased ticker now resolves back to the settled code, so the value is
always there.
It is null only if that lookup misses, which means the asset is unknown to the registry — not that
the value was unobtainable. It is never the underlying ticker: a plausible-looking wrong code is
worse than an obvious gap when you are reconciling against a webhook.
quote-leg amount (quoteAmount) is untouched, which is why rate is divided — see
an order rebases only one of its two legs.
The multiplier is point-in-time
This is the part that matters most and is easiest to get wrong:
The factor we publish is the one in effect at that movement — that fill, that deposit, that withdrawal — read off the row itself. It is never today's factor, and it is never recomputed.
A corporate action changes the factor going forward; it does not change what a past transaction delivered. Applying today's multiplier to last year's fill restates history: if an asset does a 10:1 split, your entire past history would appear multiplied by ten.
Practical consequence: two movements in the same asset can carry different multipliers, and
that is correct. Do not cache "the multiplier for NVDASPV" as though it were reference data —
it is per-row data.
This is rule 3 of Solana's Scaled UI Amount extension guidance: a historical amount must be rendered with the multiplier that was in effect when the transaction happened.
A balance or a price uses TODAY's factor
The rule above is about movements: a fill, a deposit, a withdrawal each carry the factor that was in effect when they happened, and publishing them with any other would restate history.
A balance, an indicative price and a quote are not history — they describe the state right now. Their factor is by definition today's, which is precisely what the issuer calls the scaled balance: what you hold on chain times the current multiplier. Applying a historical factor to them would be as wrong as applying today's to a past fill.
In one line: a row's factor comes from the row; a current state's factor comes from the clock.
Practical consequence, and it is not a bug: GET /v2/balances and GET /v2/fills can publish
different multipliers for the same asset at the same instant. The balance carries today's;
the fill carries the one from the day it executed.
multiplier: "1" does not mean "not a tokenized equity"A factor of exactly 1 is simply a factor that has not moved yet — TSLASPV sits at 1
today. Do not use it to classify assets. Asset class is reference data and is read from
GET /v2/assets/{assetSymbol}.
The ledger does not accept assetFormat
GET /v2/ledger deliberately exposes settled amounts, with no assetFormat.
The ledger is the double-entry record of what actually moved, and its entries do not
currently carry the multiplier that was in effect when they were booked. Honouring
assetFormat there would mean resolving the factor live, which is exactly the
restatement bug described above. A correct unscaled figure beats a scaled, possibly false
one.
To see a fill in share terms, read it from /v2/fills, which does carry its point-in-time
factor.
Nor does balance history
GET /v2/balances/{assetSymbol}/history publishes settled quantities and takes no
assetFormat either, for the same underlying reason as the ledger.
A rebased figure needs the factor that was in force when that figure was true. This series is
computed from ledger movements, and each point arrives with only its date and its balance: there is
no per-point factor, and none can be recovered. Honouring assetFormat would mean applying
today's factor to every past point — after a 10:1 split, every earlier point would jump tenfold.
That is exactly the history-rewriting the rule above forbids.
We prefer a correct unscaled series to a scaled and possibly false one. It is a real gap and not a preference: it closes when each point carries its own factor.
The request does accept either spelling — NVDAX reads NVDASPV's series — because that only
selects which asset is read. And assetSymbol in the response always carries the settled code, so
the label matches the units you are reading.
The precision of a rebased amount
Multiplying by the factor manufactures decimals. An 8-decimal settled balance times a 16-decimal factor is a 24-decimal product, and that tail is arithmetic, not information.
Every rebased amount is truncated to the precision Skipo operates that asset at — the
amountIncrement published by GET /v2/assets, documented as the step below which an amount is
truncated. It truncates down, never rounds up: on a published balance it never overstates what you
hold, and on an amount travelling out to be executed it never sells more than you asked for.
The multiplier published beside it is not truncated: it is the factor, and you need it whole
to convert.