nautechsystems/nautilus_trader · error · anyhow::Error

Pool identifier must start with '0x', was: {value}

Error message

Pool identifier must start with '0x', was: {value}

What it means

`PoolIdentifier::new_checked` parses a string into a pool identifier and first requires the canonical `0x` hex prefix. Any input lacking the prefix is rejected with this message, which echoes the offending value. This is a strict-validation constructor: it exists precisely so malformed identifiers fail loudly instead of being silently accepted.

Source

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

impl PoolIdentifier {
    /// Creates a new [`PoolIdentifier`] instance with correctness checking.
    ///
    /// Automatically detects variant based on string length:
    /// - 42 characters (0x + 40 hex): Address variant
    /// - 66 characters (0x + 64 hex): `PoolId` variant
    ///
    /// # Errors
    ///
    /// Returns an error if:
    /// - String doesn't start with "0x"
    /// - Length is neither 42 nor 66 characters
    /// - Contains invalid hex characters
    /// - Address checksum validation fails (for Address variant)
    pub fn new_checked<T: AsRef<str>>(value: T) -> anyhow::Result<Self> {
        let value = value.as_ref();

        if !value.starts_with("0x") {
            anyhow::bail!("Pool identifier must start with '0x', was: {value}");
        }

        match value.len() {
            42 => {
                validate_hex_string(value)?;

                // Parse without strict checksum validation, then normalize to checksummed format
                let addr = value
                    .parse::<Address>()
                    .map_err(|e| anyhow::anyhow!("Invalid address: {e}"))?;

                // Store the checksummed version
                Ok(Self::Address(Ustr::from(addr.to_checksum(None).as_str())))
            }
            66 => {
                // PoolId variant (32 bytes)
                validate_hex_string(value)?;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Normalize the input to include the `0x` prefix before constructing: if it doesn't start with `0x`, prepend it.
  2. Validate the string shape (prefix, length 42 for addresses, hex charset) at the ingestion boundary.
  3. Ensure the data source preserves the canonical `0x`-prefixed form (fix exporters/scripts that strip it).
  4. Use the lenient constructor only if unprefixed input is genuinely acceptable in your context.

Example fix

// before
let id = PoolIdentifier::new_checked(raw_addr)?;
// after
let normalized = if raw_addr.starts_with("0x") {
    raw_addr.to_string()
} else {
    format!("0x{raw_addr}")
};
let id = PoolIdentifier::new_checked(&normalized)?;
Defensive patterns

Strategy: validation

Validate before calling

fn has_hex_prefix(s: &str) -> bool {
    s.starts_with("0x") && s.len() > 2
}
// call PoolIdentifier::new_checked only after has_hex_prefix passes

Try / catch

match PoolIdentifier::new_checked(value) {
    Ok(id) => id,
    Err(e) if e.to_string().contains("must start with '0x'") => {
        let fixed = format!("0x{}", value.trim_start_matches("0x"));
        PoolIdentifier::new_checked(&fixed)?
    },
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `PoolIdentifier::new_checked` with strings like `abc123...`, bare hex without `0x`, checksummed-but-unprefixed addresses, or identifiers sourced from configs/logs that strip the prefix.

Common situations: Loading pool addresses from CSV/config where a tool stripped `0x`; hand-typing an address; interop with systems that represent addresses without the EIP-55/`0x` convention; mixing `new` (lenient) and `new_checked` (strict) expectations.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


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