nautechsystems/nautilus_trader · error

Failed to decode pool ID hex: {e}

Error message

Failed to decode pool ID hex: {e}

What it means

`PoolIdentifier::to_pool_id_bytes` decodes the PoolId variant's hex string into a fixed `[u8; 32]`. It errors when called on the `Address` variant, or when the stored hex cannot be decoded into exactly 32 bytes.

Source

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

            Self::Address(s) => Address::parse_checksummed(s.as_str(), None)
                .map_err(|e| anyhow::anyhow!("Failed to parse address: {e}")),
            Self::PoolId(_) => anyhow::bail!("Cannot convert PoolId variant to Address"),
        }
    }

    /// Converts to native bytes array (V4 pools only).
    ///
    /// Returns the 32-byte pool ID for use in V4-specific operations.
    ///
    /// # Errors
    ///
    /// 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) {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Match on the variant and only call `to_pool_id_bytes()` for `PoolIdentifier::PoolId`.
  2. For Address variants use `to_address()` and convert explicitly if a 32-byte form is truly needed.
  3. Re-derive the identifier via `from_pool_id_hex` to guarantee a valid 64-char hex before conversion.

Example fix

// before
let bytes = identifier.to_pool_id_bytes()?;
// after
let bytes = match &identifier {
    PoolIdentifier::PoolId(_) => identifier.to_pool_id_bytes()?,
    PoolIdentifier::Address(_) => bail!("expected PoolId variant"),
};
Defensive patterns

Strategy: type-guard

Validate before calling

fn expect_pool_id(id: &PoolIdentifier) -> Option<&Ustr> {
    match id { PoolIdentifier::PoolId(p) => Some(p), _ => None }
}

Type guard

fn is_pool_id_variant(id: &PoolIdentifier) -> bool {
    matches!(id, PoolIdentifier::PoolId(_))
}

Try / catch

match identifier.to_pool_id_bytes() {
    Ok(bytes) => bytes,
    Err(e) if e.to_string().contains("Address") => handle_address_variant(),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `to_pool_id_bytes()` on a `PoolIdentifier::Address`, or on a PoolId whose hex is corrupt/wrong-length (should be prevented by construction, but possible if built through other paths).

Common situations: Code handling a heterogeneous collection of pool identifiers uniformly; hex values mutated or sourced from external feeds without validation.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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