{"record":{"id":"d80672b47fd83ddd","repo":"nautechsystems/nautilus_trader","slug":"missing-topic-at-index-index","errorCode":null,"errorMessage":"Missing topic at index {index}","messagePattern":"Missing topic at index (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/rpc/log.rs","lineNumber":128,"sourceCode":"    anyhow::ensure!(\n        bytes.len() == Address::len_bytes(),\n        \"Invalid contract address length: expected {} bytes, was {}\",\n        Address::len_bytes(),\n        bytes.len()\n    );\n    Ok(Address::from_slice(&bytes))\n}\n\n/// Extract topic bytes at index.\n///\n/// # Errors\n///\n/// Returns an error if the topic at the specified index is missing.\npub fn extract_topic_bytes(log: &RpcLog, index: usize) -> anyhow::Result<Vec<u8>> {\n    let hex = log\n        .topics\n        .get(index)\n        .ok_or_else(|| anyhow::anyhow!(\"Missing topic at index {index}\"))?;\n    decode_hex(hex)\n}\n\n/// Extract address from topic at index.\n///\n/// In Ethereum event logs, indexed address parameters are stored as 32-byte\n/// values with the 20-byte address right-aligned (padded with zeros on the left).\n///\n/// # Errors\n///\n/// Returns an error if the topic is missing or the address extraction fails.\npub fn extract_address_from_topic(\n    log: &RpcLog,\n    index: usize,\n    description: &str,\n) -> anyhow::Result<Address> {\n    let bytes = extract_topic_bytes(log, index)\n        .map_err(|_| anyhow::anyhow!(\"Missing {description} address in topic{index}\"))?;","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/rpc/log.rs#L110-L146","documentation":"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).","triggerScenarios":"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.","commonSituations":"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).","solutions":["Check log.topics.len() before accessing higher indices.","Verify topic0 against the expected event signature hash before parsing topics.","Handle the specific index defensively: treat missing topics as an incompatible event and skip.","Update your ABI/event layout assumptions if the contract changed."],"exampleFix":"// before\nlet topic = extract_topic_bytes(&log, 1)?;\n// after\nif log.topics.len() <= 1 {\n    return Err(anyhow::anyhow!(\"event has no indexed param 1\"));\n}\nlet topic = extract_topic_bytes(&log, 1)?;","handlingStrategy":"validation","validationCode":"fn has_topic(log: &RpcLog, index: usize) -> bool { log.topics.len() > index }","typeGuard":"fn topic_at<'a>(log: &'a RpcLog, i: usize) -> Option<&'a str> { log.topics.get(i).map(|s| s.as_str()) }","tryCatchPattern":"match extract_topic_bytes(&log, 1) {\n    Ok(t) => t,\n    Err(_) => { skip_incompatible_event(&log); Vec::new() }\n}","preventionTips":["Verify topic0 matches the expected event signature before parsing.","Check topics length before accessing higher indices.","Remember anonymous events and non-indexed params reduce topic count.","Update decoder layouts when contract ABIs change."],"tags":["blockchain","ethereum","topics","index-out-of-bounds"],"backgroundTag":"index-out-of-range","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}