nautechsystems/nautilus_trader · error
Failed to derive order hash: {e}
Error message
Failed to derive order hash: {e} What it means
expected_order_id computes the EIP-712/keccak order hash via order_hash(order, neg_risk) to derive the venue order ID. When hash computation fails (malformed order fields, invalid salt/expiration encoding, or signer/wallet construction problems), the underlying error is wrapped as "Failed to derive order hash: {e}".
Source
Thrown at crates/adapters/polymarket/src/execution/order_builder.rs:224
anyhow::bail!(
"Polymarket market BUY amount {} pUSD truncates to zero at {LOT_SIZE_SCALE} decimal places",
amount.normalize(),
);
}
let (maker_amount, taker_amount) =
compute_market_maker_taker_amounts(price, amount, side, tick_decimals);
self.build_and_sign(token_id, side, maker_amount, taker_amount, "0", neg_risk)
}
/// Computes the Polymarket order ID for a signed CLOB V2 order.
pub fn expected_order_id(
&self,
order: &PolymarketOrder,
neg_risk: bool,
) -> anyhow::Result<VenueOrderId> {
let hash = order_hash(order, neg_risk)
.map_err(|e| anyhow::anyhow!("Failed to derive order hash: {e}"))?;
let order_id = format!("{hash:#x}");
Ok(VenueOrderId::from(order_id.as_str()))
}
/// Validates a limit order before building, returning a denial reason if invalid.
pub fn validate_limit_order(order: &OrderAny) -> Result<(), OrderDeniedReason> {
if order.is_reduce_only() {
return Err(validation_failed(
"Reduce-only orders not supported on Polymarket",
));
}
if order.order_type() != OrderType::Limit {
return Err(OrderDeniedReason::UnsupportedOrderType {
order_type: order.order_type(),
});
}
View on GitHub (pinned to 18893faf8b)
Solutions
- Read the wrapped inner error ({e}) — it names the exact encoding or wallet failure to fix.
- Ensure the order was built by PolymarketOrderBuilder::build_* so all EIP-712 fields (salt, maker, signer, expiration, neg_risk) are populated.
- Verify the neg_risk flag matches the market being traded.
Example fix
// before let id = builder.expected_order_id(&hand_built_order, false)?; // hand-built, missing fields // after let poly_order = builder.build_limit_order(&order, ...)?; // builder populates all hash inputs let id = builder.expected_order_id(&poly_order, market_neg_risk)?;
Defensive patterns
Strategy: try-catch
Validate before calling
if order.maker.is_empty() || order.salt.is_empty() || order.signature.is_empty() { return Err("order missing EIP-712 fields".into()); } Type guard
fn is_hashable(order: &PolymarketOrder) -> bool {
!order.maker.is_empty() && !order.signer.is_empty() && !order.salt.is_empty()
} Try / catch
match builder.expected_order_id(&order, neg_risk) {
Ok(id) => id,
Err(e) => { tracing::error!("order hash derivation failed: {e:#}"); return Err(e); }
} Prevention
- Only derive order IDs from orders produced by PolymarketOrderBuilder.
- Verify neg_risk per market before hashing.
- Log the full error chain ({e:#} with anyhow) to see the root cause.
When it happens
Trigger: Calling expected_order_id with a PolymarketOrder whose fields cannot be encoded per the CLOB EIP-712 schema, or with an incorrect neg_risk flag relative to the market, or when the signing wallet/key material is invalid.
Common situations: Passing neg_risk=false for a neg-risk market (or vice versa); orders built outside the normal builder path with missing maker/salt fields; corrupted or misconfigured private key used by the signer.
Related errors
- EIP-712 signing failed: {e}
- Polymarket {signature_type:?} signature type requires a fund
- Failed to build market order: {e}
- signature_hex called before sign
- unmapped_in_scope_message("open order", instrument_id, Some(
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/831674cec2128240.
Report an issue: GitHub.