C++ SDK
Client
Connection management, SSL/TLS configuration, and reconnection strategies for the C++ SDK
Client
The Client class manages WebSocket connections to LX, handling authentication, connection pooling, automatic reconnection, and request/response correlation.
Connection Architecture
+--------------------------------------------------+
| Client |
| +--------------------------------------------+ |
| | Connection Pool | |
| | +--------+ +--------+ +--------+ | |
| | | Conn 0 | | Conn 1 | | Conn 2 | ... | |
| | +--------+ +--------+ +--------+ | |
| +--------------------------------------------+ |
| | Request Router (Round-Robin) | |
| +--------------------------------------------+ |
| | Response Correlator (Lock-Free) | |
| +--------------------------------------------+
+--------------------------------------------------+Basic Usage
Creating a Client
#include <lxdex/client.hpp>
using namespace lxdex;
// Simple construction
Client client("wss://api.lux.network/ws");
// With options
Client client({
.endpoints = {
"wss://api.lux.network/ws",
"wss://api2.lux.network/ws" // Failover
},
.api_key = "your-api-key",
.api_secret = "your-api-secret"
});Connection Lifecycle
#include <lxdex/client.hpp>
#include <iostream>
int main() {
using namespace lxdex;
Client client({
.endpoints = {"wss://api.lux.network/ws"},
.api_key = std::getenv("LX_API_KEY")
});
// Register lifecycle callbacks
client.on_connect([](const ConnectionInfo& info) {
std::cout << "Connected to " << info.endpoint
<< " (latency: " << info.latency_us << "us)\n";
});
client.on_disconnect([](const DisconnectReason& reason) {
std::cout << "Disconnected: " << reason.message << '\n';
});
client.on_error([](const Error& error) {
std::cerr << "Error: " << error.message << '\n';
});
// Connect with timeout
auto result = client.connect(std::chrono::seconds{10});
if (!result) {
std::cerr << "Connection failed: " << result.error() << '\n';
return 1;
}
// Client is now connected and authenticated
std::cout << "Session ID: " << client.session_id() << '\n';
// Graceful shutdown
client.disconnect();
return 0;
}Client Configuration
Full Configuration Options
struct ClientConfig {
// Network
std::vector<std::string> endpoints;
std::string api_key;
std::string api_secret;
std::optional<std::string> proxy_url;
// Connection pool
std::size_t pool_size = 4;
bool use_kernel_bypass = false; // io_uring/DPDK
// Timeouts
std::chrono::milliseconds connect_timeout{5000};
std::chrono::milliseconds request_timeout{1000};
std::chrono::milliseconds heartbeat_interval{30000};
// SSL/TLS
TlsConfig tls;
// Reconnection
ReconnectConfig reconnect;
// Memory
std::size_t write_buffer_size = 64 * 1024;
std::size_t read_buffer_size = 64 * 1024;
// Threading
std::size_t io_threads = 1;
std::optional<std::vector<int>> cpu_affinity;
};TLS Configuration
struct TlsConfig {
bool verify_peer = true;
bool verify_host = true;
std::string ca_cert_path; // CA certificate file
std::string client_cert_path; // Client certificate (mTLS)
std::string client_key_path; // Client private key (mTLS)
std::string cipher_list; // OpenSSL cipher string
TlsVersion min_version = TlsVersion::TLS_1_3;
};
// Example: Production TLS setup
Client client({
.endpoints = {"wss://api.lux.network/ws"},
.tls = {
.verify_peer = true,
.verify_host = true,
.ca_cert_path = "/etc/ssl/certs/ca-certificates.crt",
.min_version = TlsVersion::TLS_1_3
}
});
// Example: Mutual TLS (mTLS)
Client client({
.endpoints = {"wss://secure.lux.network/ws"},
.tls = {
.verify_peer = true,
.ca_cert_path = "/path/to/ca.pem",
.client_cert_path = "/path/to/client.pem",
.client_key_path = "/path/to/client.key"
}
});Reconnection Strategy
struct ReconnectConfig {
bool enabled = true;
std::size_t max_attempts = 10;
std::chrono::milliseconds initial_delay{100};
std::chrono::milliseconds max_delay{30000};
double backoff_multiplier = 2.0;
bool jitter = true; // Randomize delays
};
// Example: Aggressive reconnection for HFT
Client client({
.endpoints = {"wss://api.lux.network/ws"},
.reconnect = {
.enabled = true,
.max_attempts = 100,
.initial_delay = std::chrono::milliseconds{10},
.max_delay = std::chrono::milliseconds{1000},
.backoff_multiplier = 1.5
}
});
// Example: Fail-fast for latency-critical paths
Client client({
.reconnect = {.enabled = false} // Fail immediately
});Connection Pool
The client maintains a pool of connections for load distribution and redundancy.
Pool Configuration
Client client({
.endpoints = {
"wss://api.lux.network/ws",
"wss://api2.lux.network/ws"
},
.pool_size = 8, // 8 connections per endpoint
});
// Query pool status
auto status = client.pool_status();
std::cout << "Active connections: " << status.active << '\n';
std::cout << "Pending requests: " << status.pending_requests << '\n';Connection Selection
// Default: Round-robin
client.set_connection_strategy(ConnectionStrategy::RoundRobin);
// Latency-based: Route to fastest connection
client.set_connection_strategy(ConnectionStrategy::LowestLatency);
// Affinity: Sticky sessions per symbol
client.set_connection_strategy(ConnectionStrategy::SymbolAffinity);Authentication
API Key Authentication
Client client({
.endpoints = {"wss://api.lux.network/ws"},
.api_key = "pk_live_...",
.api_secret = "sk_live_..."
});
// Authentication happens automatically on connect
client.connect();JWT Authentication
Client client({
.endpoints = {"wss://api.lux.network/ws"}
});
// Set JWT token (can be refreshed)
client.set_auth_token("eyJhbGciOiJIUzI1NiIs...");
// Refresh token before expiry
client.on_token_expiring([&client](std::chrono::seconds remaining) {
if (remaining < std::chrono::seconds{60}) {
auto new_token = refresh_token(); // Your refresh logic
client.set_auth_token(new_token);
}
});Signature-Based Authentication (HFT)
#include <lxdex/auth/signer.hpp>
// Create Ed25519 signer
auto signer = Signer::from_private_key("/path/to/private.key");
Client client({
.endpoints = {"wss://api.lux.network/ws"},
.signer = std::move(signer)
});
// Each request is automatically signed
// Signature: Ed25519(timestamp || nonce || body)Request/Response
Synchronous Requests
// Blocking call with timeout
auto result = client.request(
"lx_getOrderBook",
{{"symbol", "BTC-USD"}, {"depth", 10}},
std::chrono::milliseconds{100}
);
if (result) {
auto& book = *result;
std::cout << "Best bid: " << book["bids"][0]["price"] << '\n';
}Asynchronous Requests
// Future-based
auto future = client.request_async(
"lx_placeOrder",
{{"symbol", "BTC-USD"}, {"side", "buy"}, {"price", 50000}}
);
// Non-blocking check
if (future.wait_for(std::chrono::microseconds{10}) == std::future_status::ready) {
auto result = future.get();
// Process...
}Callback-Based (Zero Allocation)
// Pre-allocate response buffer
alignas(64) std::array<char, 4096> buffer;
client.request_callback(
"lx_getOrder",
{{"orderId", order_id}},
[&buffer](std::span<const char> response) {
// Response arrives here on I/O thread
// Zero-copy: response points directly to network buffer
std::memcpy(buffer.data(), response.data(), response.size());
}
);Subscriptions
Market Data Streams
// Order book subscription
auto sub = client.subscribe("orderbook", {{"symbol", "BTC-USD"}},
[](const json& update) {
// Called on I/O thread
}
);
// Unsubscribe
sub.cancel();
// Or use RAII
{
auto sub = client.subscribe(...);
// Auto-unsubscribe when sub goes out of scope
}Multiple Subscriptions
// Subscribe to multiple symbols
client.subscribe_batch({
{"orderbook", {{"symbol", "BTC-USD"}}},
{"orderbook", {{"symbol", "ETH-USD"}}},
{"trades", {{"symbol", "BTC-USD"}}}
}, [](const std::string& channel, const json& data) {
if (channel == "orderbook") {
// Handle order book
} else if (channel == "trades") {
// Handle trades
}
});Error Handling
Error Types
enum class ErrorCode {
// Connection errors
ConnectionFailed,
ConnectionLost,
ConnectionTimeout,
TlsError,
// Authentication errors
AuthenticationFailed,
InvalidCredentials,
TokenExpired,
// Request errors
InvalidRequest,
MethodNotFound,
RateLimited,
InsufficientBalance,
// Internal errors
InternalError,
Timeout
};
struct Error {
ErrorCode code;
std::string message;
std::optional<int> rpc_code;
std::chrono::system_clock::time_point timestamp;
};Error Handling Patterns
// Using std::expected (C++23)
auto result = client.place_order(order);
if (!result) {
handle_error(result.error());
return;
}
auto& placed_order = *result;
// Exception mode (opt-in)
client.enable_exceptions(true);
try {
auto order = client.place_order_throwing(order);
} catch (const lxdex::ConnectionError& e) {
// Handle connection issues
} catch (const lxdex::RateLimitError& e) {
std::this_thread::sleep_for(e.retry_after);
}Metrics and Monitoring
// Get connection metrics
auto metrics = client.metrics();
std::cout << "Messages sent: " << metrics.messages_sent << '\n';
std::cout << "Messages received: " << metrics.messages_received << '\n';
std::cout << "Bytes sent: " << metrics.bytes_sent << '\n';
std::cout << "Bytes received: " << metrics.bytes_received << '\n';
std::cout << "Latency p50: " << metrics.latency_p50_us << "us\n";
std::cout << "Latency p99: " << metrics.latency_p99_us << "us\n";
std::cout << "Reconnections: " << metrics.reconnection_count << '\n';
// Reset metrics
client.reset_metrics();
// Prometheus export
std::cout << client.metrics_prometheus();RAII and Lifetime
class TradingEngine {
lxdex::Client client_;
std::unique_ptr<lxdex::Subscription> book_sub_;
public:
TradingEngine(const Config& cfg)
: client_({
.endpoints = {cfg.endpoint},
.api_key = cfg.api_key
})
{
client_.connect();
book_sub_ = std::make_unique<lxdex::Subscription>(
client_.subscribe("orderbook", {{"symbol", "BTC-USD"}},
[this](const json& update) { on_book_update(update); }
)
);
}
~TradingEngine() {
// RAII: Subscriptions auto-cancel, connections auto-close
// Explicit shutdown for graceful disconnect
client_.disconnect();
}
private:
void on_book_update(const json& update);
};Thread Safety
| Method | Thread Safety |
|---|---|
connect() | Call from single thread |
disconnect() | Call from single thread |
request() | Thread-safe |
request_async() | Thread-safe |
subscribe() | Thread-safe |
metrics() | Thread-safe (atomic reads) |
| Callbacks | Invoked on I/O thread |
Best Practices
- Reuse clients: Create once, use for application lifetime
- Use connection pool: Set
pool_sizebased on request volume - Handle reconnection: Always register
on_disconnectcallback - Prefer async: Use
request_asyncfor non-blocking operations - Pin I/O threads: Set
cpu_affinityfor deterministic latency - Monitor metrics: Track latency and error rates
Example: Production Client Setup
#include <lxdex/client.hpp>
#include <csignal>
#include <atomic>
std::atomic<bool> running{true};
void signal_handler(int) {
running.store(false, std::memory_order_release);
}
int main() {
std::signal(SIGINT, signal_handler);
std::signal(SIGTERM, signal_handler);
using namespace lxdex;
Client client({
.endpoints = {
"wss://api.lux.network/ws",
"wss://api2.lux.network/ws"
},
.api_key = std::getenv("LX_API_KEY"),
.api_secret = std::getenv("LX_API_SECRET"),
.pool_size = 4,
.tls = {
.verify_peer = true,
.min_version = TlsVersion::TLS_1_3
},
.reconnect = {
.enabled = true,
.max_attempts = 50
},
.io_threads = 2,
.cpu_affinity = {0, 1}
});
client.on_connect([](const ConnectionInfo& info) {
spdlog::info("Connected to {} ({}us)", info.endpoint, info.latency_us);
});
client.on_disconnect([](const DisconnectReason& reason) {
spdlog::warn("Disconnected: {}", reason.message);
});
if (auto result = client.connect(); !result) {
spdlog::error("Failed to connect: {}", result.error().message);
return 1;
}
// Main loop
while (running.load(std::memory_order_acquire)) {
// Trading logic...
std::this_thread::sleep_for(std::chrono::milliseconds{1});
}
spdlog::info("Shutting down...");
client.disconnect();
return 0;
}