Skip to main content

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:

ValueWhat you get
rebased (default)Amounts in terms of the underlying equity — what a holder thinks of as "their shares".
baseAmounts in terms of the settled SPV tokens — what moved on chain and in the ledger.

Where it is accepted:

ResourceassetFormat
Orders and fills — GET /v2/orders, /v2/orders/{id}, /v2/orders/{id}/fills, /v2/fillsyes — defaults to rebased
Withdrawals — GET /v2/withdrawalsyes — defaults to rebased
Deposits — GET /v2/depositsyes — defaults to rebased
Balances — GET /v2/balancesyes — defaults to rebased
Indicative price — GET /v2/markets/{market}/priceyes — defaults to rebased
Quotes — POST /v2/quotesyes, in the body — defaults to rebased
Ledger — GET /v2/ledgerno — always settled (why)
Balance history — GET /v2/balances/{assetSymbol}/historyno — always settled (why)
Webhooksno — 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)
On quotes the parameter goes in the BODY

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.

The default is rebased, on all of them

This 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:

CodeWhat it isWhere it appears
NVDASPVThe settled SPV token — what moved on chain and in the ledgerassetSymbol in reference data, base.asset, the ledger, the market id
NVDAXThe underlying share — what a holder thinks they ownbaseAsset 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 only

It 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"
}
}
The base block is READ, not derived — and you should not derive it either

The 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.