nautechsystems/nautilus_trader · error

Missing topic at index {index}

Error message

Missing topic at index {index}

What it means

This error means log.topics has no entry at the requested index when extract_topic_bytes is called. Ethereum topics are indexed 0..3 (topic0 = event signature); requesting an index beyond the topic count indicates a wrong event layout assumption or a log from a different event. The topic's hex is then hex-decoded (which can itself fail with a decode error).

Source

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

    anyhow::ensure!(
        bytes.len() == Address::len_bytes(),
        "Invalid contract address length: expected {} bytes, was {}",
        Address::len_bytes(),
        bytes.len()
    );
    Ok(Address::from_slice(&bytes))
}

/// Extract topic bytes at index.
///
/// # Errors
///
/// Returns an error if the topic at the specified index is missing.
pub fn extract_topic_bytes(log: &RpcLog, index: usize) -> anyhow::Result<Vec<u8>> {
    let hex = log
        .topics
        .get(index)
        .ok_or_else(|| anyhow::anyhow!("Missing topic at index {index}"))?;
    decode_hex(hex)
}

/// Extract address from topic at index.
///
/// In Ethereum event logs, indexed address parameters are stored as 32-byte
/// values with the 20-byte address right-aligned (padded with zeros on the left).
///
/// # Errors
///
/// Returns an error if the topic is missing or the address extraction fails.
pub fn extract_address_from_topic(
    log: &RpcLog,
    index: usize,
    description: &str,
) -> anyhow::Result<Address> {
    let bytes = extract_topic_bytes(log, index)
        .map_err(|_| anyhow::anyhow!("Missing {description} address in topic{index}"))?;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check log.topics.len() before accessing higher indices.
  2. Verify topic0 against the expected event signature hash before parsing topics.
  3. Handle the specific index defensively: treat missing topics as an incompatible event and skip.
  4. Update your ABI/event layout assumptions if the contract changed.

Example fix

// before
let topic = extract_topic_bytes(&log, 1)?;
// after
if log.topics.len() <= 1 {
    return Err(anyhow::anyhow!("event has no indexed param 1"));
}
let topic = extract_topic_bytes(&log, 1)?;
Defensive patterns

Strategy: validation

Validate before calling

fn has_topic(log: &RpcLog, index: usize) -> bool { log.topics.len() > index }

Type guard

fn topic_at<'a>(log: &'a RpcLog, i: usize) -> Option<&'a str> { log.topics.get(i).map(|s| s.as_str()) }

Try / catch

match extract_topic_bytes(&log, 1) {
    Ok(t) => t,
    Err(_) => { skip_incompatible_event(&log); Vec::new() }
}

Prevention

When it happens

Trigger: Calling extract_topic_bytes(log, 1..) on an anonymous event or an event with fewer indexed parameters than assumed; calling it on a log from a different event type than expected.

Common situations: Contract upgrades changing indexed parameters, parsing logs for one event with a decoder built for another, or events with zero indexed args (topics containing only topic0).

Related errors


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