nautechsystems/nautilus_trader · error

Pool fee is invalid

Error message

Pool fee is invalid

What it means

Each pool entry in the deployment manifest must carry a nonzero swap fee that does not exceed 1,000,000 (fee tier in pool-fee units, e.g. Uniswap-style granularity). The library enforces this because a zero fee is nonsensical for a live pool and an out-of-range value indicates a corrupted or fabricated pool record.

Source

Thrown at crates/adapters/blockchain/src/rpc/verification.rs:1383

        );
    }

    for pool in &manifest.pools {
        for address in [
            &pool.address,
            &pool.token0,
            &pool.token1,
            &pool.factory,
            &pool.quote_contract,
        ] {
            let address = Address::from_str(address)
                .map_err(|_| anyhow::anyhow!("Pool manifest address is invalid"))?;
            anyhow::ensure!(
                contract_addresses.contains(&address),
                "Pool manifest references an unpinned contract"
            );
        }
        anyhow::ensure!(
            pool.fee != 0 && pool.fee <= 1_000_000,
            "Pool fee is invalid"
        );
    }
    let mut call_edges = HashSet::new();
    for edge in &manifest.call_edges {
        anyhow::ensure!(
            matches!(
                edge.purpose.as_str(),
                "wrap" | "approve" | "swap_sell" | "swap_buy"
            ) && matches!(
                edge.call_type.as_str(),
                "call" | "staticcall" | "delegatecall" | "callcode"
            ),
            "Deployment manifest call edge is invalid"
        );
        let caller = Address::from_str(&edge.caller)
            .map_err(|_| anyhow::anyhow!("Call edge address is invalid"))?;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Set pool.fee to the pool's real fee tier (e.g. 500, 3000, 10000 for Uniswap v3-style tiers)
  2. If the fee was expressed in percent or basis points, convert it to the manifest's fee units before writing it
  3. Remove placeholder pool entries that were never fully populated
  4. Regenerate the manifest from the source of truth (chain data / generator) instead of editing by hand

Example fix

// before
{"address": "0xabc...", "fee": 0}
// after
{"address": "0xabc...", "fee": 3000}
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check fee before building the manifest
if !(pool.fee > 0 && pool.fee <= 1_000_000) {
    return Err(format!("pool {} fee {} out of range (1..=1_000_000)", pool.address, pool.fee));
}

Type guard

fn is_valid_fee(fee: u64) -> bool { fee != 0 && fee <= 1_000_000 }

Try / catch

match validate_deployment_manifest(&manifest) {
    Err(e) if e.to_string().contains("Pool fee is invalid") => {
        eprintln!("Set pool.fee to a nonzero value <= 1_000_000 (fee-tier units)");
    }
    Err(e) => return Err(e),
    Ok(()) => {}
}

Prevention

When it happens

Trigger: Validating a manifest whose pool.fee equals 0 or is greater than 1,000,000; the check runs after the pool address/pinning validation passes.

Common situations: Leaving fee at its default 0 when hand-authoring a pool entry; copying a fee from a venue that uses a different unit (e.g. percent 0.3 instead of 3000); a typo like 10000000 instead of 1000000.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


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