nautechsystems/nautilus_trader · error · anyhow::Error
Restored pool {instrument_id} has no fee
Error message
Restored pool {instrument_id} has no fee What it means
restore_swap_plan (crates/adapters/blockchain/src/execution/client.rs:878) reads pool.fee as a U24 for the restored pool; when the cached Pool for the instrument has fee: None the reconciliation aborts. The fee is required to rebuild swap calldata and quote-token math for the restored instrument. A fee-less pool means the pool was registered or restored without its fee parameter - incomplete instrument metadata rather than an on-chain anomaly.
Source
Thrown at crates/adapters/blockchain/src/execution/client.rs:878
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(
"e_token.symbol,
quote_token.decimals,
0,
"e_token.name,
CurrencyType::Crypto,
)?;
let token_in = pool.get_base_token().address;
let token_out = quote_token.address;
Ok(SwapPlan {
order,
quote_currency,
pool,
instrument_id,
pool_address,View on GitHub (pinned to 2114cf6f76)
Solutions
- Check how the pool for the failing instrument got registered and ensure its fee tier (e.g. 3000 for 0.3%) is provided.
- Refresh the pool from an authoritative source so fee is populated, then reconnect.
- If the pool row in Postgres lacks the fee, update it through the ingestion source (not by hand-editing live rows if avoidable).
- Resolve or complete the active intent once pool metadata is correct.
Defensive patterns
Strategy: try-catch
Validate before calling
// Rust: verify instrument pools carry a fee tier before booting reconciliation
// (resolve_pool must produce pools with Some(fee), e.g. 500/3000/10000 for Uniswap V3)
for instrument in active_swap_instruments() {
let pool = client.resolve_pool(&instrument)?;
anyhow::ensure!(pool.fee.is_some(), "pool for {instrument} lacks a fee tier");
} Type guard
fn is_pool_missing_fee(e: &anyhow::Error) -> bool {
e.to_string().contains("has no fee")
} Try / catch
if let Err(e) = client.connect().await {
if is_pool_missing_fee(&e) {
// instrument metadata incomplete: refresh pool registration with fee tiers
log::error!("pool registered without fee tier; fix ingestion then reconnect: {e}");
}
return Err(e);
} Prevention
- Require the fee tier in whatever pipeline registers pools into the cache.
- Validate pool metadata (address + fee) when instruments are configured.
- Refresh pool rows from an authoritative source before reconciling active swap intents.
When it happens
Trigger: The pool for the intent's instrument exists in the cache but was registered without a fee tier (custom registration path or data source that omits fee); a persisted pool row lacking the fee restored into memory; reconciling a swap intent against such a pool at connect().
Common situations: Custom pool ingestion pipelines that skip the fee field; pools added through config with only an address; database pool rows written before fee capture existed.
Understand the failure class
Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.
Related errors
- Persisted pool {pool_address} does not match restored pool {
- Persisted swap intent has no client order ID
- Persisted swap intent has no instrument ID
- Persisted swap instrument {instrument_id} does not match res
- Persisted swap trader does not match restored order
AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21).
Data as JSON: /api/errors/4b7b0b1676d761a3.
Report an issue: GitHub.