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
- Validate the string: after stripping `0x`, it must be non-empty, even-length, and contain only [0-9a-fA-F].
- Fix upstream slicing/indexing so you pass complete, even-length hex substrings.
- For explorer-copied values, left-pad topics/addresses to the expected byte width before decoding.
- 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
- Validate even length and hex charset before slicing/decoding log fields.
- Left-pad topic/address strings from explorers to the expected width.
- Sanitize mock/test fixtures to emit proper 0x-prefixed hex.
- Add unit tests feeding odd-length and non-hex strings to decode_hex paths.
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
- Topic must be at least 32 bytes, was {}
- Missing data in swap event log
- Failed to decode eth_call response
- Failed to parse {method} result '{hex_string}': {e}
- Invalid hex u64: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/f365429673de3862.
Report an issue: GitHub.