nautechsystems/nautilus_trader · error · anyhow::Error

`{key}` must be a positive u32

Error message

`{key}` must be a positive u32

What it means

The price-precision parser reads optional `n_sig_figs` and `mantissa` params from instrument definition params and requires each, when present, to be a JSON number representable as a u32. If the value is missing, not an integer, negative, or exceeds u32 range, it fails with "`{key}` must be a positive u32". Note the message says 'positive' but zero also technically parses; the guard exists to keep precision params valid.

Source

Thrown at crates/adapters/hyperliquid/src/data.rs:2041

    book
}

// Reads optional `nSigFigs` / `mantissa` L2 precision controls from
// `subscribe_params`; bails on non-positive integer values.
pub(crate) fn parse_book_precision_params(
    params: Option<&Params>,
) -> anyhow::Result<(Option<u32>, Option<u32>)> {
    let Some(params) = params else {
        return Ok((None, None));
    };

    let read_u32 = |key: &str| -> anyhow::Result<Option<u32>> {
        match params.get(key) {
            None => Ok(None),
            Some(v) => v
                .as_u64()
                .and_then(|n| u32::try_from(n).ok())
                .ok_or_else(|| anyhow::anyhow!("`{key}` must be a positive u32"))
                .map(Some),
        }
    };

    Ok((read_u32("n_sig_figs")?, read_u32("mantissa")?))
}

// Hyperliquid funds perpetuals hourly, so `interval` is fixed at 60 mins;
// `time` from the venue marks the end of the funding interval in ms.
pub(crate) fn funding_entry_to_update(
    entry: &HyperliquidFundingHistoryEntry,
    instrument_id: InstrumentId,
) -> FundingRateUpdate {
    let rate = entry.funding_rate;
    let ts = UnixNanos::from(entry.time * 1_000_000);
    FundingRateUpdate::new(instrument_id, rate, Some(60), None, ts, ts)
}

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure the param is a non-negative JSON integer within u32 range (0..=4294967295).
  2. Remove quotes so the value is numeric, not a string.
  3. Omit the key entirely if you want the default (None) behavior.
  4. Validate/clamp the value before building the params map.

Example fix

// before
{"n_sig_figs": "5", "mantissa": -1}
// after
{"n_sig_figs": 5, "mantissa": null}
Defensive patterns

Strategy: validation

Validate before calling

fn valid_u32_param(v: &serde_json::Value) -> bool {
    v.as_u64().map(|n| n <= u32::MAX as u64).unwrap_or(false)
}
// before building params:
for key in ["n_sig_figs", "mantissa"] {
    if let Some(v) = params.get(key) {
        assert!(valid_u32_param(v), "{key} must be a positive u32");
    }
}

Type guard

fn is_u32(v: &serde_json::Value) -> bool {
    v.as_u64().map(|n| u32::try_from(n).is_ok()).unwrap_or(false)
}

Prevention

When it happens

Trigger: Passing params like `{"n_sig_figs": -1}`, `{"mantissa": 4294967296}`, `{"n_sig_figs": 2.5}`, or a string `"5"` instead of numeric `5` when configuring Hyperliquid instrument price precision.

Common situations: Hand-edited JSON config using strings for numbers; copy-pasted signed values; overflow when a value came from a wider type; typos like `n_sig_figs: "auto"`.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/ec9e7a0f76a7a764. Report an issue: GitHub.