nautechsystems/nautilus_trader · error

Pool ID hex must be 64 characters (32 bytes), was {}

Error message

Pool ID hex must be 64 characters (32 bytes), was {}

What it means

`PoolIdentifier::from_pool_id_hex` builds a pool identifier from a hex string, which must be 64 hex characters (32 bytes) after an optional `0x` prefix. The library throws this when the stripped string length is not 64, since a 256-bit pool ID always encodes to exactly 64 hex chars.

Source

Thrown at crates/model/src/defi/pool_identifier.rs:141

        anyhow::ensure!(
            bytes.len() == 32,
            "Pool ID must be 32 bytes, was {}",
            bytes.len()
        );

        Ok(Self::PoolId(Ustr::from(&hex::encode_prefixed(bytes))))
    }

    /// Creates a `PoolId` variant from a hex string (with or without 0x prefix).
    ///
    /// # Errors
    ///
    /// Returns an error if the string is not valid 64-character hex.
    pub fn from_pool_id_hex<T: AsRef<str>>(hex: T) -> anyhow::Result<Self> {
        let hex = hex.as_ref();
        let hex_str = hex.strip_prefix("0x").unwrap_or(hex);

        anyhow::ensure!(
            hex_str.len() == 64,
            "Pool ID hex must be 64 characters (32 bytes), was {}",
            hex_str.len()
        );

        validate_hex_string(&format!("0x{hex_str}"))?;

        Ok(Self::PoolId(Ustr::from(&format!(
            "0x{}",
            hex_str.to_lowercase()
        ))))
    }

    /// Returns the inner identifier value as a Ustr.
    #[must_use]
    pub fn inner(&self) -> Ustr {
        match self {
            Self::Address(s) | Self::PoolId(s) => *s,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Trim the input and confirm `len == 64` (after stripping `0x`) before calling.
  2. If working with token/base addresses, use the Address constructor instead.
  3. Validate the string is pure hex with `validate_hex_string` or a regex like `^0x[0-9a-fA-F]{64}$`.
  4. Ensure config/env sources are not truncating or padding the value.

Example fix

// before
let id = PoolIdentifier::from_pool_id_hex(token_address_hex)?; // 40 chars
// after
assert_eq!(hex_str.trim_start_matches("0x").len(), 64);
let id = PoolIdentifier::from_pool_id_hex(pool_id_hex)?;
Defensive patterns

Strategy: validation

Validate before calling

fn is_valid_pool_id_hex(s: &str) -> bool {
    let h = s.strip_prefix("0x").unwrap_or(s);
    h.len() == 64 && h.chars().all(|c| c.is_ascii_hexdigit())
}

Type guard

fn is_pool_id_hex(s: &str) -> bool { is_valid_pool_id_hex(s) }

Try / catch

match PoolIdentifier::from_pool_id_hex(input.trim()) {
    Ok(id) => id,
    Err(e) => { log::warn!("invalid pool id hex: {e}"); return Err(e); }
}

Prevention

When it happens

Trigger: Calling `from_pool_id_hex` with a 40-char address hex, a truncated hex string, extra characters (spaces, checksum chars beyond hex), or a non-hex-length string.

Common situations: Pasting an EVM address (40 hex chars) where a pool ID is expected, copying partial hex from logs, or handling strings with whitespace/newlines from config files.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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