nautechsystems/nautilus_trader · error

`InstrumentId` not applicable to `Block`

Error message

`InstrumentId` not applicable to `Block`

What it means

Data::instrument_id() panics for variants that conceptually have no instrument ID — Block and PoolSnapshot. The method is only meaningful for pool events (swaps, liquidity updates, fee events). The panic guards against silently returning a meaningless default identifier.

Source

Thrown at crates/model/src/defi/data/mod.rs:226

            Self::PoolSnapshot(snapshot) => snapshot.ts_init,
            Self::PoolSwap(swap) => swap.ts_init,
            Self::PoolLiquidityUpdate(update) => update.ts_init,
            Self::PoolFeeCollect(collect) => collect.ts_init,
            Self::PoolFeeProtocolUpdate(update) => update.ts_init,
            Self::PoolFeeProtocolCollect(collect) => collect.ts_init,
            Self::PoolFlash(flash) => flash.ts_init,
        }
    }

    /// Returns the instrument ID associated with this DeFi data.
    ///
    /// # Panics
    ///
    /// Panics if the variant is a `Block` or `PoolSnapshot` where instrument IDs are not applicable.
    #[must_use]
    pub fn instrument_id(&self) -> InstrumentId {
        match self {
            Self::Block(_) => panic!("`InstrumentId` not applicable to `Block`"), // TBD?
            Self::PoolSnapshot(snapshot) => snapshot.instrument_id,
            Self::PoolSwap(swap) => swap.instrument_id,
            Self::PoolLiquidityUpdate(update) => update.instrument_id,
            Self::PoolFeeCollect(collect) => collect.instrument_id,
            Self::PoolFeeProtocolUpdate(update) => update.instrument_id,
            Self::PoolFeeProtocolCollect(collect) => collect.instrument_id,
            Self::Pool(pool) => pool.instrument_id,
            Self::PoolFlash(flash) => flash.instrument_id,
        }
    }
}

impl HasTsInit for DefiData {
    fn ts_init(&self) -> UnixNanos {
        self.ts_init()
    }
}

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Match on the Data variant first and only call instrument_id() for PoolSwap/PoolLiquidityUpdate/PoolFee* variants
  2. Skip Block and PoolSnapshot messages in code paths that need an instrument ID
  3. Use a helper that returns Option<InstrumentId> in your own code

Example fix

// before
let id = data.instrument_id(); // panics for Block
// after
let id = match data {
    Data::Block(_) | Data::PoolSnapshot(_) => None,
    _ => Some(data.instrument_id()),
};
Defensive patterns

Strategy: type-guard

Validate before calling

fn instrument_id_of(data: &Data) -> Option<InstrumentId> {
    match data {
        Data::Block(_) | Data::PoolSnapshot(_) => None,
        _ => Some(data.instrument_id()),
    }
}

Type guard

fn has_instrument_id(data: &Data) -> bool {
    !matches!(data, Data::Block(_) | Data::PoolSnapshot(_))
}

Try / catch

let id = std::panic::catch_unwind(|| data.instrument_id()).ok();

Prevention

When it happens

Trigger: Calling data.instrument_id() on a Data::Block(_) or Data::PoolSnapshot(_) value, typically in generic code that processes every message from a DEX subscription identically.

Common situations: Generic data handlers/routers that dispatch on instrument_id without filtering by data type; test harnesses iterating all Data variants; subscribing to block streams and reusing pool-event handling code.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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