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

MethodThread 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)
CallbacksInvoked on I/O thread

Best Practices

  1. Reuse clients: Create once, use for application lifetime
  2. Use connection pool: Set pool_size based on request volume
  3. Handle reconnection: Always register on_disconnect callback
  4. Prefer async: Use request_async for non-blocking operations
  5. Pin I/O threads: Set cpu_affinity for deterministic latency
  6. 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;
}