nautechsystems/nautilus_trader · error · anyhow::Error

Unknown fee token '{fee_token}' with non-zero fee {fee_amoun

Error message

Unknown fee token '{fee_token}' with non-zero fee {fee_amount}

What it means

resolve_fee_currency maps a fill's fee token to a known currency. If the token is neither registered, nor a side token, nor resolvable via the instrument, and the fee is non-zero (so it cannot be silently ignored with a quote fallback), the parser bails with this error naming the unknown token and amount.

Source

Thrown at crates/adapters/hyperliquid/src/http/parse.rs:747

            );
        }
        return Ok(instrument.quote_currency());
    }

    if let Some(currency) = Currency::try_from_str(fee_token) {
        return Ok(currency);
    }

    if fee_amount.is_zero() {
        let fallback = instrument.quote_currency();
        log::debug!(
            "Unregistered fee token '{fee_token}' on zero-fee fill for {}; using {fallback} as fallback",
            instrument.id(),
        );
        return Ok(fallback);
    }

    anyhow::bail!("Unknown fee token '{fee_token}' with non-zero fee {fee_amount}")
}

fn is_outcome_side_token(symbol: &str) -> bool {
    let Some(rest) = symbol.strip_prefix('+') else {
        return false;
    };
    !rest.is_empty() && rest.bytes().all(|b| b.is_ascii_digit())
}

// Hyperliquid documents a venue-wide minimum order notional: $10 for perps,
// and 10 quote_token for spot.
// https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/error-responses
const HYPERLIQUID_MIN_ORDER_NOTIONAL: Decimal = Decimal::TEN;

/// Converts a single Hyperliquid instrument definition into a Nautilus `InstrumentAny`.
///
/// Returns `None` if the conversion fails (e.g., unsupported market type).
///

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Register the unknown token as a Currency in the adapter's currency registry so it can be resolved.
  2. Update the Hyperliquid adapter to a version that knows the new fee token.
  3. Inspect the raw fill payload to confirm the venue's fee token; if it is wrong for this market, report the venue/API inconsistency.
  4. If the fee were zero this would fall back to the quote currency — for non-zero fees the exact currency must be resolvable, so ensure correct instrument/currency initialization at connect time.

Example fix

// before
// fill arrives with fee token "XYZ" not in registry -> Unknown fee token 'XYZ' with non-zero fee 1.5
// after
if let Some(cur) = currency_registry.get("XYZ") { /* fee resolvable */ } else {
    currency_registry.register(Currency::from("XYZ", 8)); // register new venue token
}
Defensive patterns

Strategy: try-catch

Validate before calling

fn fee_token_known(token: &str, registry: &CurrencyRegistry) -> bool {
    registry.get(token).is_some()
}

Try / catch

match resolve_fee_currency(fee_token, fee_amount, instrument) {
    Ok(cur) => apply_fee(cur, fee_amount),
    Err(e) if e.to_string().contains("Unknown fee token") => {
        log::warn!("unregistered fee token; register it and re-process fill: {e}");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: parse_fill_report receiving a fill whose fee token string is not a known currency for the instrument and carries a non-zero fee — e.g. a new/unregistered token appears as fee denomination in the venue fill response.

Common situations: New tokens listed on Hyperliquid before the adapter's currency registry includes them; fee reported in an unexpected asset (e.g. USDT instead of USDC); adapter currency registry not initialized with all venue tokens.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


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