Every market has tick size, step size, and minimums. The SDK loads spot and Zipper catalogs so decimal inputs can be converted to wire integers safely.
Wait for ready
Order and trigger write paths wait for catalog hydration when hydrate_catalogs: true (default). You can still call this explicitly before reads that need symbol ids/scales:
client.wait_for_catalogs().await?;Hydration is fail-closed: wait_for_catalogs returns an error when the attempt fails. See Catalog.
Decimal inputs
Order writes take typed Price / Quantity on create params (see Orders). Prefer decimal constructors for human inputs;
use ticks / scaled integers for bots. Strict decimal form is digits with an optional fractional
part (65000 / 65000.5); bare trailing dots (65000.) are rejected. Leading/trailing
whitespace is trimmed before validation.
Unscaled decimal quantities need a symbol (or an already-scaled Quantity) so the SDK can resolve
catalog scale. base_quantity_scale_for_symbol returns Option<u32> and never invents scale 8
for unknown or unhydrated symbols. Write paths fail as Error::Validation instead of
guessing. Scale-dependent reads and streams fail the same way when the catalog scale is unknown.
Scale 0 is valid for whole-unit assets and is distinct from a missing scale.
Protocol scale ceiling
Quantity and ledger formatters, parsers, and catalog hydration reject scales above MAX_PROTOCOL_SCALE (36). That ceiling is an SDK safety bound against pathological padding;
it is not the ledger canonical scale. Ledger balances and amount_e18 remain fixed at
scale 18. Trading quantity scales come from the spot catalog and are typically well below 36.
Error::Validation before send.Scaled integers (bots)
use polyester::types::{Price, Quantity, QuantityDomain};
let price = Price::from_ticks(100_000_000_000, None)?; // 100 at the protocol scale of 1e9 per tick
let scale = client
.catalogs
.base_quantity_scale_for_symbol("BTC-USDT")
.ok_or_else(|| polyester::Error::validation("BTC-USDT quantity scale is unavailable"))?;
let qty = Quantity::from_scaled(
1_000_000,
Some(scale),
QuantityDomain::OrderBase,
Some("BTC-USDT".into()),
None,
)?;Protocol price ticks use a fixed 1e9 scale (one tick is 0.000000001 quote units); the server
still validates market tick size. Quote budgets use the pair's quote_quantity_scale, from catalogs.quote_quantity_scale_for_symbol.
Immutable money metadata
Price and Quantity metadata is fixed by their validated constructors. Read it through
immutable getters:
use polyester::types::{Price, Quantity, QuantityDomain};
let scale = 8;
let price = Price::from_ticks(100_000_000_000, None)?;
let qty = Quantity::from_scaled(
1_000_000,
Some(scale),
QuantityDomain::OrderBase,
Some("BTC-USDT".into()),
None,
)?;
assert_eq!(price.symbol(), None);
assert_eq!(qty.scale(), Some(scale));
assert_eq!(qty.domain(), QuantityDomain::OrderBase);
assert_eq!(qty.symbol(), Some("BTC-USDT"));
assert_eq!(qty.symbol_id(), None);Metadata cannot be reassigned or supplied through struct literals; construct a new validated value instead.
let id = client.catalogs.symbol_id_for_symbol("BTC-USDT");
let scale = client.catalogs.base_quantity_scale_for_symbol("BTC-USDT");Also see Scaled integers and Public IDs.