nautechsystems/nautilus_trader · error

Restored pool has no fee

Error message

Restored pool {instrument_id} has no fee

What it means

restore_swap_plan needs the Uniswap V3 fee tier (U24) of the restored pool to build the swap parameters. If the resolved pool has no fee value (pool.fee is None), the swap plan cannot be constructed and reconciliation aborts. The library throws this because a Uniswap V3 swap cannot be encoded without a fee tier.

Solutions

  1. Fix the instrument/pool registration so the Uniswap V3 fee tier is populated (re-register the pool or enable fee discovery) and restart.
  2. Verify the instrument provider config points at the correct factory/pool so fee metadata is fetched.
  3. Restore a complete cache snapshot that includes the pool's fee metadata.
  4. As a last resort, resolve the pending intent manually in the durable store, since the swap cannot be rebuilt from a fee-less pool.
Defensive patterns

Strategy: validation

Validate before calling

let pool = client.resolve_pool(&instrument_id)?;
if pool.fee.is_none() {
    eprintln!("pool for {instrument_id} has no fee tier; fix instrument registration before connecting");
}

Type guard

fn has_fee(pool: &Pool) -> bool {
    pool.fee.is_some()
}

Prevention

When it happens

Trigger: reconcile_unresolved_execution or build_execution_verification_migration calls restore_swap_plan; resolve_pool(&instrument_id) returns a pool whose fee field is None. Happens when the pool definition for the instrument is incomplete in the instrument provider/cache, e.g. fee not queried/set at registration or a partially-defined instrument restored from cache.

Common situations: Instrument provider configured without fee discovery (fee never populated for that pool); custom/edited instrument definitions missing the fee attribute; adapter version where fee was optional combined with a pool source that omits it; cache restored from an incomplete snapshot.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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

Appendix: source

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

            intent
                .pool_address
                .as_deref()
                .ok_or_else(|| anyhow::anyhow!("Persisted swap intent has no pool address"))?,
        )?;
        anyhow::ensure!(
            pool.address == pool_address,
            "Persisted pool {pool_address} does not match restored pool {}",
            pool.address
        );
        let amount_in = U256::from_str(
            intent
                .amount_in
                .as_deref()
                .ok_or_else(|| anyhow::anyhow!("Persisted swap intent has no input amount"))?,
        )?;
        let fee = U24::try_from(
            pool.fee
                .ok_or_else(|| anyhow::anyhow!("Restored pool {instrument_id} has no fee"))?,
        )?;
        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,
        )?;
        let (token_in, token_out) = swap_token_pair(
            order.order_side(),
            pool.get_base_token().address,
            quote_token.address,
        )?;
        let factory = self.uniswap_v3_factory()?;
        anyhow::ensure!(
            pool.dex.factory == factory,
            "Restored pool {instrument_id} references factory {}, expected registered factory {factory}",

View on GitHub (pinned to 18893faf8b)