nautechsystems/nautilus_trader · error · anyhow::Error

Invalid hex characters in: {s}

Error message

Invalid hex characters in: {s}

What it means

validate_hex_string checks that all characters after the mandatory '0x' prefix are ASCII hex digits. Any non-hex character after '0x' is rejected with this error. It backs new_checked and from_pool_id_hex.

Source

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

    ///
    /// Returns error if this is an Address variant or if hex decoding fails.
    pub fn to_pool_id_bytes(&self) -> anyhow::Result<[u8; 32]> {
        match self {
            Self::PoolId(s) => {
                let hex_str = s.strip_prefix("0x").unwrap_or(s.as_str());
                hex::decode_array::<32>(hex_str)
                    .map_err(|e| anyhow::anyhow!("Failed to decode pool ID hex: {e}"))
            }
            Self::Address(_) => anyhow::bail!("Cannot convert Address variant to PoolId bytes"),
        }
    }
}

/// Validates that a string contains only valid hexadecimal characters after "0x" prefix.
fn validate_hex_string(s: &str) -> anyhow::Result<()> {
    let hex_part = &s[2..];
    if !hex_part.chars().all(|c| c.is_ascii_hexdigit()) {
        anyhow::bail!("Invalid hex characters in: {s}");
    }
    Ok(())
}

impl PartialEq for PoolIdentifier {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::Address(a), Self::Address(b)) | (Self::PoolId(a), Self::PoolId(b)) => {
                // Case-insensitive comparison
                a.eq_ignore_ascii_case(b)
            }
            // Different variants are never equal
            _ => false,
        }
    }
}

impl Eq for PoolIdentifier {}

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Inspect the string for non [0-9a-fA-F] characters after '0x'
  2. Strip whitespace and trailing punctuation before constructing
  3. Validate with a regex/scan before calling new_checked or from_pool_id_hex

Example fix

// before
let pid = PoolIdentifier::new_checked("0x 9243eb4d")?;
// after
let cleaned = value.trim();
assert!(cleaned[2..].chars().all(|c| c.is_ascii_hexdigit()));
let pid = PoolIdentifier::new_checked(cleaned)?;
Defensive patterns

Strategy: validation

Validate before calling

fn is_valid_hex_id(s: &str) -> bool {
    s.len() >= 2 && s.starts_with("0x") && s[2..].chars().all(|c| c.is_ascii_hexdigit())
}

Prevention

When it happens

Trigger: Passing a pool identifier or pool ID containing non-hex characters after '0x' — e.g. whitespace, placeholders like '0x<pool_id>', or labels appended to the ID.

Common situations: Copy-paste errors from docs or explorers including spaces or annotations; template placeholders not substituted; encoding bugs producing non-ASCII lookalike characters.

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/e0910bdc46978add. Report an issue: GitHub.