nautechsystems/nautilus_trader · error · anyhow::Error

Pool {instrument_id} has no fee tier

Error message

Pool {instrument_id} has no fee tier

What it means

While preparing a swap order, the resolved pool for the instrument has fee = None. Uniswap V3 pools always have a fee tier (in hundredths of a bip, e.g. 500 = 0.05%), and the swap calldata cannot be built without it, so the order is rejected. The None comes from the pool metadata in the cache (fee is Option<u32>, populated from pool registry/PoolCreated data).

Source

Thrown at crates/adapters/blockchain/src/execution/client.rs:1178

            );
        }

        if order.order_side() != OrderSide::Sell {
            anyhow::bail!(
                "Unsupported order side {}; only Sell is supported",
                order.order_side()
            );
        }

        if order.is_quote_quantity() {
            anyhow::bail!(
                "Quote-denominated quantities are not supported; quantity must be denominated in the base token"
            );
        }

        let fee = pool
            .fee
            .ok_or_else(|| anyhow::anyhow!("Pool {instrument_id} has no fee tier"))?;
        let fee = U24::try_from(fee)
            .map_err(|_| anyhow::anyhow!("Pool {instrument_id} fee {fee} exceeds uint24"))?;

        let base_token = pool.get_base_token();
        let quote_token = pool.get_quote_token();
        let quote_currency = Currency::new_checked(
            &quote_token.symbol,
            quote_token.decimals,
            0,
            &quote_token.name,
            CurrencyType::Crypto,
        )?;

        if !self
            .transaction_limits
            .allowed_token_pairs
            .contains(&(base_token.address, quote_token.address))
        {

View on GitHub (pinned to 2114cf6f76)

Solutions

  1. Check the pool definition for the instrument in the adapter configuration and confirm it includes the fee tier
  2. Verify on-chain that the pool address is a real Uniswap V3 pool and read its fee() (immutable) to get the correct value
  3. Restart/refresh the data subscription so the pool cache repopulates with complete metadata before submitting orders
  4. Use a fee value in hundredths of a bip (500 for 0.05%, 3000 for 0.30%, 10000 for 1%)

Example fix

# before: pool registered without a fee tier
Pool(instrument_id=WETH_USDC, address="0x...", fee=None)

# after: fee tier present in Uniswap V3 units (0.05%)
Pool(instrument_id=WETH_USDC, address="0x...", fee=500)
Defensive patterns

Strategy: validation

Validate before calling

# Before trading, confirm every configured pool exposes a fee tier
for instrument_id in instruments_to_trade:
    pool = get_pool_definition(instrument_id)
    assert pool.fee is not None, f'{instrument_id} has no fee tier configured'
    assert 0 < pool.fee <= 0xFFFFFF, f'{instrument_id} fee out of Uniswap V3 range'

Prevention

When it happens

Trigger: submit_order on an instrument whose cached pool definition was registered without a fee tier: instrument/pool configuration missing the fee field, or a PoolCreated event observed without fee data before full pool initialization.

Common situations: Adding a new pool to the adapter config and forgetting the fee tier; pointing the instrument provider at a custom factory whose PoolCreated event omits fee; race at startup where the pool is resolved before its metadata is complete.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21). Data as JSON: /api/errors/b2e74b7b0eeeeb9e. Report an issue: GitHub.