nautilus_lighter/common/consts.rs
1// -------------------------------------------------------------------------------------------------
2// Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3// https://nautechsystems.io
4//
5// Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6// You may not use this file except in compliance with the License.
7// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Venue identifiers and tuning constants for the Lighter adapter.
17
18use std::{sync::LazyLock, time::Duration};
19
20use nautilus_model::identifiers::{ClientId, Venue};
21use ustr::Ustr;
22
23/// Venue name string for Lighter.
24pub const LIGHTER: &str = "LIGHTER";
25
26/// Venue name string for Robinhood Chain.
27pub const LIGHTER_ROBINHOOD: &str = "LIGHTER_ROBINHOOD";
28
29/// Lighter venue identifier.
30pub static LIGHTER_VENUE: LazyLock<Venue> = LazyLock::new(|| Venue::new(Ustr::from(LIGHTER)));
31
32/// Robinhood Chain venue identifier.
33pub static LIGHTER_ROBINHOOD_VENUE: LazyLock<Venue> =
34 LazyLock::new(|| Venue::new(Ustr::from(LIGHTER_ROBINHOOD)));
35
36/// Static client ID instance.
37pub static LIGHTER_CLIENT_ID: LazyLock<ClientId> =
38 LazyLock::new(|| ClientId::new(Ustr::from(LIGHTER)));
39
40/// Robinhood Chain client ID instance.
41pub static LIGHTER_ROBINHOOD_CLIENT_ID: LazyLock<ClientId> =
42 LazyLock::new(|| ClientId::new(Ustr::from(LIGHTER_ROBINHOOD)));
43
44/// L2 chain id for Lighter Mainnet.
45///
46/// Mirrors the upstream `lighter-go` constant. Used as the first element of
47/// the L2 transaction hash preimage.
48pub const LIGHTER_MAINNET_CHAIN_ID: u32 = 304;
49
50/// L2 chain id for Lighter Testnet.
51///
52/// Mirrors `lighter-go`'s testnet chain id and matches the value the oracle
53/// generator emits.
54pub const LIGHTER_TESTNET_CHAIN_ID: u32 = 300;
55
56/// L2 chain id for Robinhood Mainnet.
57pub const LIGHTER_ROBINHOOD_CHAIN_ID: u32 = 466_324;
58
59/// Nautilus integrator account index on Lighter.
60pub const LIGHTER_NAUTILUS_INTEGRATOR_ACCOUNT_INDEX: u64 = 723_813;
61
62/// Venue error code for missing integrator approval.
63pub const LIGHTER_ERROR_CODE_INTEGRATOR_NOT_APPROVED: u64 = 21_149;
64
65/// Venue error code for an invalid (non-contiguous) transaction nonce.
66pub const LIGHTER_ERROR_CODE_INVALID_NONCE: i64 = 21_104;
67
68/// Venue error code for an idempotent duplicate WebSocket subscription.
69pub const LIGHTER_ERROR_CODE_ALREADY_SUBSCRIBED: u64 = 30_003;
70
71/// Venue error code for WebSocket request rate limiting.
72pub const LIGHTER_ERROR_CODE_WS_RATE_LIMITED: u64 = 30_009;
73
74/// Venue error code for a failed WebSocket subscription open.
75pub const LIGHTER_ERROR_CODE_WS_SUBSCRIBE_FAILED: u64 = 30_012;
76
77/// Venue error-code range for L2 transaction failures.
78///
79/// Observed codes follow a domain split: `20xxx` request validation, `21xxx`
80/// transaction processing (21104 invalid nonce, 21149 integrator not
81/// approved), `30xxx` WebSocket subscription state (30003 "Already
82/// Subscribed"). Bare error frames are attributed to in-flight `sendTx`
83/// requests only when the code falls in this range.
84pub const LIGHTER_ERROR_CODE_TX_RANGE: std::ops::Range<u64> = 21_000..22_000;
85
86/// Public docs anchor for integrator approval.
87pub const LIGHTER_INTEGRATOR_APPROVAL_DOCS_URL: &str =
88 "https://nautilustrader.io/docs/nightly/integrations/lighter.html#integrator-attribution";
89
90/// Maximum batch size for `sendTxBatch` on the WebSocket transport.
91pub const LIGHTER_MAX_BATCH_TX: usize = 15;
92
93/// Maximum auth-token expiry permitted by the venue (8 hours).
94pub const LIGHTER_AUTH_TOKEN_MAX_TTL: Duration = Duration::from_secs(8 * 60 * 60);
95
96/// Default refresh window before an auth token expires.
97///
98/// The adapter rotates the auth token this far ahead of expiry to avoid races
99/// during long-running WebSocket sessions.
100pub const LIGHTER_AUTH_TOKEN_REFRESH_LEAD: Duration = Duration::from_secs(15 * 60);
101
102/// Default WebSocket heartbeat interval.
103///
104/// Lighter requires a frame at least every 2 minutes; we send well below that.
105pub const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(30);
106
107/// Teardown window for a WebSocket carrying no inbound frame of any kind.
108///
109/// Liveness rests on the venue still sending frames rather than on market data
110/// arriving: a quiet market is legitimate, and the pong answering every
111/// [`HEARTBEAT_INTERVAL`] ping refreshes this window even when nothing trades.
112/// Three heartbeat cycles tolerate two lost replies before teardown.
113pub const HEARTBEAT_TIMEOUT: Duration =
114 Duration::from_secs(HEARTBEAT_INTERVAL.as_secs().saturating_mul(3));
115
116const _: () = assert!(
117 HEARTBEAT_TIMEOUT.as_secs() > HEARTBEAT_INTERVAL.as_secs(),
118 "heartbeat timeout must exceed the heartbeat interval, or every connection tears down \
119 before its first pong is due"
120);
121
122/// Base reconnect backoff for the WebSocket client.
123pub const RECONNECT_BASE_BACKOFF: Duration = Duration::from_millis(250);
124
125/// Maximum reconnect backoff for the WebSocket client.
126pub const RECONNECT_MAX_BACKOFF: Duration = Duration::from_secs(30);
127
128/// Default HTTP request timeout.
129pub const HTTP_TIMEOUT: Duration = Duration::from_secs(10);
130
131/// Grace period a client waits for its background tasks to finish during
132/// teardown before aborting them.
133pub const DISCONNECT_TIMEOUT: Duration = Duration::from_secs(2);
134
135/// Maximum subscribe messages awaiting venue acknowledgement at once.
136///
137/// Held below Lighter's 50-per-IP inflight cap; see the WebSocket rate-limit
138/// strategy in [`crate::common::rate_limit`].
139pub const SUBSCRIBE_INFLIGHT_MAX: usize = 35;
140
141/// Maximum venue-level retries for one WebSocket subscription request.
142pub const SUBSCRIBE_RETRY_MAX: u8 = 5;
143
144/// Initial backoff after a venue-level WebSocket subscription rejection.
145pub const SUBSCRIBE_RETRY_BASE_BACKOFF: Duration = Duration::from_millis(250);
146
147/// Outbound command queue depth before backpressure kicks in.
148pub const QUEUE_MAX: usize = 1000;