Scaled integers are how Polyester represents decimal values in typed protobuf API contracts.
These fields represent prices, quantities, balances, fees, and chart series as integer values. Direct protobuf clients must determine which scale applies before parsing or encoding values.
If you use the official Polyester TypeScript, Python, or Go SDK, normal amount conversion is handled by the SDK. This guide is mainly for developers who work with protobuf contracts directly or build lower-level ConnectRPC tooling.
"qty": "0.00100000" and "price": "50000.000000", so REST clients do not need to decode *_scaled, *_ticks, U128, or _e18 protobuf fields. Continue reading only if you work directly with protobuf or ConnectRPC payloads.The short version
Use this mental model:
- Trading prices use 6-decimal ticks: fields named
*_ticksmake this explicit; market-data OHLC fields such asopen,high,low, andcloseuse the same scale without the suffix. - Quantities use asset scale: trading pair quantities use
base_quantity_scaleorquote_quantity_scalefromGetSpotConfigResponse.pairs; asset-only APIs usequantity_scalefromGetSpotConfigResponse.assets. - Ledger and chain canonical amount fields use 18 decimals:
U128ledger balance fields and fields namedamount_e18are 18-decimal integer values. Non-amountU128identifiers are not decimals. - Chart series may use their own scale:
*_qfields are compact chart values and must be read from the field's API documentation. Do not use them as authoritative balances. - Basis points are not scaled decimals:
*_bpsfields are direct integer basis points.
marketdata.v1.MarketDataService.GetSpotConfig, while ledger and chain canonical amounts use 18 decimals.float, double, JavaScript Number, Python float, or Go float64. Parse user input as an exact decimal string, shift the decimal point by the scale, reject unsupported fractional precision, and only then encode the integer.Why APIs use scaled integers
Scaled integers avoid floating-point drift, keep protobuf payloads compact, reduce serialization and deserialization work, and make matching, balance, and settlement calculations deterministic. For trading activity, those encoding costs matter because busy clients may send and receive many prices, quantities, fills, balances, and book levels per second.
The conversion rule is:
scaled integer = decimal value * 10^scale
decimal value = scaled integer / 10^scaleFor example, if BTC has quantity_scale = 8, then 0.001 BTC is encoded as:
0.001 * 10^8 = 100000Transport behavior
| Interface | Representation | Example |
|---|---|---|
| REST JSON | decimal or integer strings | "amount": "0.5", "nonce": "123" |
| ConnectRPC JSON/protojson | integer strings for 64-bit values | "qty_scaled": "100000", "price_ticks": "50000000000" |
| ConnectRPC protobuf | integer fields | qty_scaled = 100000, price_ticks = 50000000000 |
ConnectRPC clients use generated integer types and decode using the field's scale. REST amount
strings are decimals, while non-amount U128 identifiers such as withdraw nonce are unsigned
integer strings.
If you build directly on generated protobuf types in TypeScript or JavaScript, do not store 64-bit scaled values in Number. Use your generated protobuf type, bigint, or string-backed helpers. For U128 values, ConnectRPC JSON/protojson uses { "hi": "...", "lo": "..." }.
Scale registry
| Field shape | Scale source | How to decode | Common examples |
|---|---|---|---|
*_ticks price fields | Fixed 6 decimals | value / 1_000_000 | price_ticks, avg_price_ticks, trigger_price_ticks, close_ticks, mid_ticks |
| Unsuffixed OHLC price fields | Fixed 6 decimals | value / 1_000_000 | candle open, high, low, close |
Base quantity *_scaled fields | Pair base_quantity_scale | value / 10^base_quantity_scale | qty_scaled, new_qty_scaled, executed_qty_scaled, orderbook quantities |
Quote quantity *_scaled fields | Pair quote_quantity_scale | value / 10^quote_quantity_scale | quote volume, quote-side fees, quote notional-style values |
| Unsuffixed base volume fields | Pair base_quantity_scale | value / 10^base_quantity_scale | candle volume, reference_volume |
| Asset quantity fields | Asset quantity_scale | value / 10^quantity_scale | supply values |
Ledger U128 balances | Fixed 18 decimals | combine hi and lo, then divide by 10^18 | trading, funding, reserved balances |
amount_e18 | Fixed 18 decimals | combine hi and lo, then divide by 10^18 | internal transfers, chain lifecycle, and withdraw amounts |
*_q chart fields | Field-specific | Read the field docs; never use as authoritative money | balance history, equity history, supply series |
*_bps | Basis points | integer basis points; divide by 100 only when rendering a percent | slippage, trailing distance, 24h change |
The suffix alone is not enough for every *_scaled field. Use the field comment and denomination to choose base, quote, or asset scale.
Trading prices
Trading prices use ticks with a fixed 6-decimal scale. This is independent of the quote asset's quantity scale.
{
"price_ticks": "50000000000"
}Decode it as:
50000000000 / 10^6 = 50000.000000Use this rule for fields such as price_ticks, avg_price_ticks, trigger_price_ticks, limit_price_ticks, activation_price_ticks, market-data OHLC prices, and heatmap fields such as mid_ticks.
Slippage and trailing distance tick fields use the same 1e-6 quote-unit tick space, but they are price deltas rather than absolute prices. For example, market_max_slippage_ticks, max_slippage_ticks, and trailing_distance_ticks are added to or subtracted from a reference price.
Price protection fields often appear as oneof alternatives. Send either the tick form, such as market_max_slippage_ticks, or the basis-point form, such as market_max_slippage_bps, not both.
open, high, low, and close use the same 6-decimal price scale without _ticks. Candle volume uses the pair's base_quantity_scale without _scaled. These fields keep chart payloads compact, so the protobuf field comment is authoritative.price_ticks with quote_quantity_scale. Price ticks use 6 decimals even when the quote asset has a different quantity scale.Trading quantities
Trading order quantities use the pair's base asset scale.
To decode pair-specific quantities, fetch GetSpotConfigResponse and locate the pair in pairs by symbol_id or symbol. For BTC-USDT, if that PairConfig says:
{
"symbol_id": 1,
"symbol": "BTC-USDT",
"base_quantity_scale": 8,
"quote_quantity_scale": 6
}Then this ConnectRPC payload:
{
"qty_scaled": "100000"
}Means:
100000 / 10^8 = 0.001 BTCUse the base scale for order quantities, orderbook quantities, executed quantities, and base volume fields.
Order read quantities use the _scaled suffix too: fields such as orig_qty_scaled, cum_qty_scaled, and leaves_qty_scaled use the pair's base_quantity_scale.
Order placement has two explicit sizing alternatives. base_qty_scaled uses base_quantity_scale. max_quote_debit_scaled uses quote_quantity_scale because it is a hard
all-in quote budget. Preview and create responses return resolved_base_qty_scaled using the base
scale and can echo submitted_max_quote_debit_scaled using the quote scale.
See Order Sizing for eligibility and
acknowledgement semantics.
Use the quote scale for quote-denominated totals, quote volume, and quote-side fees. Use the asset quantity_scale for asset-scaled fields such as supply analytics. Internal transfers use amount_e18 instead.
quantity_display_decimals is display guidance only. Do not use it for protobuf encode or decode; use base_quantity_scale, quote_quantity_scale, or asset quantity_scale.
Fees and quote-side values
Fees and quote-side totals use the asset that the value is denominated in.
For trades, use the companion fee_asset field to choose the scale:
- If
fee_assetisQUOTEorFEE_ASSET_UNSPECIFIED, decodefee_scaledwithquote_quantity_scale. - If
fee_assetisBASE, decodefee_scaledwithbase_quantity_scale. - Referral share fields follow the same asset-denomination rule as
fee_scaled.
SELL fees are always quote-denominated. For BUY trades, fee_asset decides whether the fee is
charged in quote (QUOTE) or deducted from received base (BASE).
See Fee Assets for valid combinations.
Quote volume is also quote-denominated. Decode fields such as volume_24h_quote_scaled with the quote asset scale, not the price tick scale.
Ledger balances
Ledger balance fields use U128 integer values with 18 decimal places. This ledger scale is fixed and does not come from GetSpotConfigResponse quantity scales.
These fields are larger than regular 64-bit quantities because balances and movements must be represented without overflow across assets and accounts.
U128 is split into two 64-bit halves. Reconstruct the full integer before applying the decimal scale:
u128 = (hi << 64) | lo
decimal = u128 / 10^18For a non-zero hi example:
hi = 1, lo = 0
u128 = 18446744073709551616
decimal = 18.446744073709551616{
"trading": {
"hi": "0",
"lo": "1500000000000000000"
}
}Decode it as:
(0 << 64 | 1500000000000000000) / 10^18 = 1.5Use the ledger scale for balance fields such as trading, funding, reserved, and available.
U128 itself is only an unsigned integer container. Amount fields using U128 are 18-decimal when their field comment says so, but identifier fields are not decimals. For example, withdraw payload nonce is a raw unsigned 128-bit identifier and must not be divided by 10^18.
bigint / bigint performs integer division. 1500000000000000000n / 10n ** 18n returns 1n, not 1.5. Format bigint values as decimal strings or use an arbitrary-precision decimal library.18-decimal amount_e18 fields
Any field named amount_e18 uses fixed 18-decimal scale. This includes ledger transfer rows, order transfer legs, chain lifecycle flows, and withdraw payloads.
{
"amount_e18": {
"hi": "0",
"lo": "500000000000000000"
}
}This represents:
(0 << 64 | 500000000000000000) / 10^18 = 0.5The _e18 suffix is intentional: decode it with 18 decimals, not with GetSpotConfigResponse asset scale.
Chart series and q fields
Fields ending in _q are compact quantity-like series values. They are not one universal scale.
*_q fields are for charts and analytics. Never use balance_q, equity_q, or other *_q fields as authoritative balances, order inputs, withdrawal amounts, or settlement values. For funds, transfers, and trading, use ledger U128 balances, amount_e18, *_scaled, or *_ticks fields as appropriate.Common examples:
| Field | Meaning |
|---|---|
balance_q | balance chart values scaled by 1e7 |
equity_q | quote-equity chart values scaled by 1e4 |
btc_prices_q | BTC-USDT price series using 1e6 price tick scale |
supply_q | supply values scaled by the parent asset's quantity_scale |
total_supply_q | analytics supply values scaled by the asset quantity_scale |
total_balance_q | analytics balance values scaled by the asset quantity_scale |
When a field uses _q, check that field's API documentation. Do not infer the scale from another _q field.
Some _q series and delta fields are signed. Preserve the signedness from the generated protobuf type instead of assuming every compact series is unsigned.
Basis points
Basis point fields are plain integers, not decimal fixed-point fields. Divide by 100 only when rendering a percentage for humans.
1 bp = 0.01%
100 bps = 1%
250 bps = 2.5%Use this rule for fields such as market_max_slippage_bps, trailing_distance_bps, and change_24h_bps.
Conversion cookbook
When sending a ConnectRPC request:
- Fetch and cache
GetSpotConfigResponsewhen the field uses pair or asset scale. - Find the field's denomination: base asset, quote asset, ledger amount, chain amount, price, chart value, or basis points.
- Pick the scale from the registry above.
- Parse the user decimal as an exact decimal string.
- Reject values with more fractional digits than the scale supports.
- Validate trading inputs against applicable constraints such as
tick_size,step_size, minimum quantity, and minimum notional. - Send the resulting integer in the protobuf field.
When reading a ConnectRPC response:
- Read the field suffix and field documentation.
- Resolve the scale from
GetSpotConfigResponseor from the fixed-scale registry. - Format the integer as a decimal string for display.
- Keep the original integer for follow-up API calls when possible.
Example order input:
symbol: BTC-USDT
price: 50000.000000
qty: 0.001
base_quantity_scale: 8
price_ticks = 50000.000000 * 10^6 = 50000000000
qty_scaled = 0.001 * 10^8 = 100000Common mistakes
- Do not use JavaScript
Numberfor 64-bit scaled integers. - Do not multiply or divide financial values with floating-point types.
- Do not decode a
U128fromloalone. Combinehiandlofirst. - Do not assume every amount with 18 decimals is a trading quantity.
- Do not apply 18-decimal scaling to non-amount
U128fields such asnonce. - Do not decode
price_tickswith quote asset scale. - Do not decode
fee_scaledwithout checkingfee_asset. - Do not treat
_qas a single global scale or canonical money. - Do not use
quantity_display_decimalsfor protobuf encode/decode. It is display guidance only. - Do not drop signedness from generated types; some compact series and deltas are signed.
- Do not mix decimal strings and scaled integers inside the same client path.
- Do not round silently when a user enters more fractional digits than the scale allows.
GetSpotConfigResponse once at startup, refresh it on a bounded interval, and keep a small local map from symbol_id to base and quote scales.When in doubt, prefer the protobuf field comment and GetSpotConfigResponse over examples copied from another endpoint.