# Catalog & precision

Spot/Zipper catalogs, decimal strings, and scaled-integer inputs.

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:

```rust
client.wait_for_catalogs().await?;
```

Hydration is fail-closed: `wait_for_catalogs` returns an error when the attempt fails. See [Catalog](https://testnet.polyester.com/docs/sdk/rust/reference/catalog).

## Decimal inputs

Order writes take typed `Price` / `Quantity` on create params (see [Orders](https://testnet.polyester.com/docs/sdk/rust/reference/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.

> **post\_only is limit GTC only**
>
> Invalid combinations return `Error::Validation` before send.

## Scaled integers (bots)

```rust
use polyester::types::{Price, Quantity, QuantityDomain};

let price = Price::from_ticks(100_000_000, None)?; // protocol 1e6 ticks
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 fixed 1e6 scale; the server still validates market tick size.

## Immutable money metadata

`Price` and `Quantity` metadata is fixed by their validated constructors. Read it through immutable getters:

```rust
use polyester::types::{Price, Quantity, QuantityDomain};

let scale = 8;
let price = Price::from_ticks(100_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);
```

This is an alpha breaking change. Migrate `price.symbol` to `price.symbol()` and migrate `quantity.scale`, `quantity.domain`, `quantity.symbol`, and `quantity.symbol_id` to the corresponding getter calls. Metadata cannot be reassigned or supplied through struct literals; construct a new validated value instead.

```rust
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](https://testnet.polyester.com/docs/developer-docs/connectrpc/scaled-integers) and [Public IDs](https://testnet.polyester.com/docs/developer-docs/shared-concepts/public-ids).
