nautechsystems/nautilus_trader · error · anyhow::Error

Cannot convert Address variant to PoolId bytes

Error message

Cannot convert Address variant to PoolId bytes

What it means

Variant guard in PoolIdentifier::to_pool_id_bytes: the 32-byte pool ID only exists for the PoolId variant (V4 pools); calling it on an Address-variant identifier (V2/V3 pools) has no meaningful result and bails.

Source

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

            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) {
            (Self::Address(a), Self::Address(b)) | (Self::PoolId(a), Self::PoolId(b)) => {
                // Case-insensitive comparison

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure the identifier comes from a V4 pool with a 66-char pool ID before calling to_pool_id_bytes
  2. Use to_address for V2/V3 pool identifiers
  3. Match on the enum variant and branch the conversion accordingly

Example fix

// before
let bytes = v3_pool.to_pool_id_bytes()?;
// after
if let PoolIdentifier::Address(addr) = &v3_pool {
    let bytes = addr.to_address()?;
}
Defensive patterns

Strategy: type-guard

Validate before calling

matches!(pid, PoolIdentifier::PoolId(_))

Type guard

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

Try / catch

match pid.to_pool_id_bytes() {
    Ok(bytes) => use_pool_id(bytes),
    Err(e) if e.to_string().contains("Address variant") => handle_v2_v3_pool(pid),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling pool_identifier.to_pool_id_bytes() on a value built from a 42-char EVM address (e.g. a Uniswap V2/V3 pool address).

Common situations: Passing a V2/V3 pool address into a code path expecting a V4 pool ID; mixing pool types in subscription configuration.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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