nautechsystems/nautilus_trader · error

Invalid hex: {e}

Error message

Invalid hex: {e}

What it means

The public helper `decode_hex` in the log adapter converts hex strings (with or without `0x` prefix) to bytes using the `hex` crate and wraps any decode failure in this error. It is surfaced when log-entry fields (addresses, topics, data) contain strings that are not valid hexadecimal.

Source

Thrown at crates/adapters/blockchain/src/rpc/log.rs:30

//  See the License for the specific language governing permissions and
//  limitations under the License.
// -------------------------------------------------------------------------------------------------

//! Parses Ethereum JSON-RPC log entries.
//!
//! Converts `RpcLog` fields and hex strings to their domain types.

use alloy::primitives::Address;
use nautilus_core::hex;
use nautilus_model::defi::rpc::RpcLog;

/// Decode hex string (with or without 0x prefix) to bytes.
///
/// # Errors
///
/// Returns an error if the hex string is invalid.
pub fn decode_hex(hex: &str) -> anyhow::Result<Vec<u8>> {
    hex::decode(hex.trim_start_matches("0x")).map_err(|e| anyhow::anyhow!("Invalid hex: {e}"))
}

/// Parse hex string to u64.
///
/// # Errors
///
/// Returns an error if the hex string cannot be parsed as u64.
pub fn parse_hex_u64(hex: &str) -> anyhow::Result<u64> {
    u64::from_str_radix(hex.trim_start_matches("0x"), 16)
        .map_err(|e| anyhow::anyhow!("Invalid hex u64: {e}"))
}

/// Parse hex string to u32.
///
/// # Errors
///
/// Returns an error if the hex string cannot be parsed as u32.
pub fn parse_hex_u32(hex: &str) -> anyhow::Result<u32> {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Validate the string: after stripping `0x`, it must be non-empty, even-length, and contain only [0-9a-fA-F].
  2. Fix upstream slicing/indexing so you pass complete, even-length hex substrings.
  3. For explorer-copied values, left-pad topics/addresses to the expected byte width before decoding.
  4. Use `hex::decode` in a test to reproduce and identify the exact offending character/length.

Example fix

// before
let bytes = decode_hex(data)?; // data = "0x123" (odd length) -> Invalid hex: Odd number of digits
// after
let payload = data.trim_start_matches("0x");
anyhow::ensure!(!payload.is_empty() && payload.len() % 2 == 0, "malformed hex data");
let bytes = decode_hex(data)?;
Defensive patterns

Strategy: validation

Validate before calling

fn is_valid_hex(s: &str) -> bool {
    let body = s.trim_start_matches("0x");
    !body.is_empty() && body.len() % 2 == 0
        && body.chars().all(|c| c.is_ascii_hexdigit())
}
// guard: if !is_valid_hex(log.data) { skip/log and continue }

Type guard

fn try_decode_hex(s: &str) -> Option<Vec<u8>> {
    hex::decode(s.trim_start_matches("0x")).ok()
}

Try / catch

match decode_hex(&log.data) {
    Err(e) if e.to_string().starts_with("Invalid hex:") => {
        tracing::warn!(data = %log.data, "skipping log with malformed hex data");
        continue; // or record to a dead-letter list for later inspection
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling `decode_hex` (directly or via `extract_address`, `extract_topic_bytes`, `extract_data_bytes`) with a string whose payload after stripping `0x` has odd length or contains non-hex characters — e.g. "0xZZ12", "0x123" (odd length), or an empty/non-hex data field.

Common situations: Handling logs from non-standard chains or mock servers with malformed data; off-by-one slicing producing odd-length substrings; passing UTF-8 text (method names, placeholders) instead of hex-encoded data; unpadded topic values copied from explorers.

Related errors


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