nautechsystems/nautilus_trader · error · anyhow::Error
Order expiration (until) not set
Error message
Order expiration (until) not set
What it means
Every dYdX v4 order must carry a good-til oneof (block height or timestamp expiry). OrderBuilder::build throws this when the until field (OrderGoodUntil) was never set, so the required GoodTilOneof cannot be constructed.
Source
Thrown at crates/adapters/dydx/src/grpc/order.rs:446
// Build order ID
let order_id = Some(OrderId {
subaccount_id: Some(SubaccountId {
owner: self.subaccount_owner.clone(),
number: self.subaccount_number,
}),
client_id: self.client_id,
order_flags: match self.flags {
OrderFlags::ShortTerm => 0,
OrderFlags::LongTerm => 64,
OrderFlags::Conditional => 32,
},
clob_pair_id: self.market_params.clob_pair_id,
});
// Set good til oneof - required for all orders
let until = self
.until
.ok_or_else(|| anyhow::anyhow!("Order expiration (until) not set"))?;
let good_til_oneof = match until {
OrderGoodUntil::Block(height) => Some(GoodTilOneof::GoodTilBlock(height)),
OrderGoodUntil::Time(time) => {
Some(GoodTilOneof::GoodTilBlockTime(time.as_second().try_into()?))
}
};
// Quantize price: use explicit price if set, otherwise compute worst-case for market orders
let subticks = if let Some(price) = self.price {
self.market_params.quantize_price(price)?
} else if matches!(
self.order_type,
Some(OrderType::Market | OrderType::StopMarket | OrderType::MarketIfTouched)
) {
let side = self
.side
.ok_or_else(|| anyhow::anyhow!("Order side not set"))?;View on GitHub (pinned to 18893faf8b)
Solutions
- Always call .until(OrderGoodUntil::Block(h)) or .until(OrderGoodUntil::Time(t)) before build()
- Derive expiry from the current chain height plus a buffer and set it unconditionally
- Centralize builder construction in a helper that enforces all required fields
Example fix
// before let builder = OrderBuilder::new(params).side(side).size(size); let order = builder.build()?; // after let expiry = OrderGoodUntil::Time(current_time + Duration::minutes(5)); let builder = OrderBuilder::new(params).side(side).size(size).until(expiry); let order = builder.build()?;
Defensive patterns
Strategy: validation
Validate before calling
anyhow::ensure!(until.is_some(), "good-til (until) must be set before build()");
Prevention
- Always compute expiry (block height or timestamp) at order construction time
- Wrap OrderBuilder in a domain helper that requires side, size, and until
- Review conditional-order paths for omitted setters
When it happens
Trigger: Calling build() (e.g. via build_conditional_order) without calling the until / good-til setter, or code that sets time-in-force but not the concrete expiry.
Common situations: Building a conditional order without computing an expiry block/time; refactor where expiry was set on one order path but not another; a helper that wraps the builder and omits until.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- Order side not set
- Order size not set
- slots lock poisoned
- AX whoami account {} supplied no fee rates
- DEX {} does not have initialize event set.
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/7d63876e34bb844a.
Report an issue: GitHub.