{"record":{"id":"7d63876e34bb844a","repo":"nautechsystems/nautilus_trader","slug":"order-expiration-until-not-set","errorCode":null,"errorMessage":"Order expiration (until) not set","messagePattern":"Order expiration \\(until\\) not set","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/dydx/src/grpc/order.rs","lineNumber":446,"sourceCode":"        // Build order ID\n        let order_id = Some(OrderId {\n            subaccount_id: Some(SubaccountId {\n                owner: self.subaccount_owner.clone(),\n                number: self.subaccount_number,\n            }),\n            client_id: self.client_id,\n            order_flags: match self.flags {\n                OrderFlags::ShortTerm => 0,\n                OrderFlags::LongTerm => 64,\n                OrderFlags::Conditional => 32,\n            },\n            clob_pair_id: self.market_params.clob_pair_id,\n        });\n\n        // Set good til oneof - required for all orders\n        let until = self\n            .until\n            .ok_or_else(|| anyhow::anyhow!(\"Order expiration (until) not set\"))?;\n\n        let good_til_oneof = match until {\n            OrderGoodUntil::Block(height) => Some(GoodTilOneof::GoodTilBlock(height)),\n            OrderGoodUntil::Time(time) => {\n                Some(GoodTilOneof::GoodTilBlockTime(time.as_second().try_into()?))\n            }\n        };\n\n        // Quantize price: use explicit price if set, otherwise compute worst-case for market orders\n        let subticks = if let Some(price) = self.price {\n            self.market_params.quantize_price(price)?\n        } else if matches!(\n            self.order_type,\n            Some(OrderType::Market | OrderType::StopMarket | OrderType::MarketIfTouched)\n        ) {\n            let side = self\n                .side\n                .ok_or_else(|| anyhow::anyhow!(\"Order side not set\"))?;","sourceCodeStart":428,"sourceCodeEnd":464,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/dydx/src/grpc/order.rs#L428-L464","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nlet builder = OrderBuilder::new(params).side(side).size(size);\nlet order = builder.build()?;\n// after\nlet expiry = OrderGoodUntil::Time(current_time + Duration::minutes(5));\nlet builder = OrderBuilder::new(params).side(side).size(size).until(expiry);\nlet order = builder.build()?;","handlingStrategy":"validation","validationCode":"anyhow::ensure!(until.is_some(), \"good-til (until) must be set before build()\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["dydx","builder-pattern","missing-field","order-expiry"],"backgroundTag":"missing-required-argument","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}