Skip to main content

Crate lightcone

Crate lightcone 

Source
Expand description

§Lightcone SDK

Rust SDK for the Lightcone impact market protocol on Solana.

§Table of Contents

§Installation

Add to your Cargo.toml:

[dependencies]
lightcone = { version = "0.4.1", features = ["native"] }

For browser/WASM targets:

[dependencies]
lightcone = { version = "0.4.1", features = ["wasm"] }

§Feature Flags

FeatureWhat it enablesUse case
nativehttp + native-auth + ws-native + solana-rpcMarket makers, bots, CLI tools
wasmhttp + ws-wasmBrowser applications

§Quick Start

use lightcone::prelude::*;
use lightcone::auth::native::sign_login_message;
use solana_keypair::Keypair;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Defaults to Prod. Use .env(LightconeEnv::Staging) for staging.
    let client = LightconeClient::builder()
        .deposit_source(DepositSource::Market)
        .build()?;
    let keypair = Keypair::new();

    // 1. Authenticate
    let nonce = client.auth().get_nonce().await?;
    let signed = sign_login_message(&keypair, &nonce);
    let user = client.auth().login_with_message(
        &signed.message,
        &signed.signature_bs58,
        &signed.pubkey_bytes,
        None,
    ).await?;

    // 2. Find a market
    let market = client.markets().get(None, Some(1)).await?.markets.into_iter().next().unwrap();
    let orderbook = &market.orderbook_pairs[0];

    // 3. Deposit collateral to the global pool
    let deposit_mint = market.deposit_assets[0].pubkey().to_pubkey()?;
    let deposit_ix = client.positions().deposit().await
        .user(keypair.pubkey())
        .mint(deposit_mint)
        .amount(1_000_000)
        .build_ix()
        .await?;

    // 4. Build, sign, and submit a limit order
    let request = client.orders().limit_order().await
        .maker(keypair.pubkey())
        .bid()
        .price("0.55")
        .size("100")
        .sign(&keypair, &orderbook)?;

    let response = client.orders().submit(&request).await?;
    println!("Order submitted: {:?}", response);

    // 5. Withdraw from the global pool
    let withdraw_ix = client.positions().withdraw().await
        .user(keypair.pubkey())
        .mint(deposit_mint)
        .amount(1_000_000)
        .build_ix()
        .await?;

    // 6. Stream real-time updates
    let mut ws = client.ws_native();
    ws.connect().await?;
    ws.subscribe(SubscribeParams::Books {
        orderbook_ids: vec![orderbook.orderbook_id.clone()],
    })?;

    Ok(())
}

§Start Trading

use lightcone::prelude::*;
use solana_keypair::read_keypair_file;
use solana_signer::Signer;

// Defaults to Prod. Use .env(LightconeEnv::Staging) for staging.
let client = LightconeClient::builder()
    .deposit_source(DepositSource::Market)
    .build()?;
let keypair = read_keypair_file("~/.config/solana/id.json")?;

§Step 1: Find a Market

let market = client.markets().get(None, Some(1)).await?.markets.into_iter().next().unwrap();
let orderbook = market
    .orderbook_pairs
    .iter()
    .find(|pair| pair.active)
    .or_else(|| market.orderbook_pairs.first())
    .expect("market has no orderbooks");

§Step 2: Deposit Collateral

let deposit_mint = market.deposit_assets[0].pubkey().to_pubkey()?;
let deposit_ix = client.positions().deposit().await
    .user(keypair.pubkey())
    .mint(deposit_mint)
    .amount(1_000_000)
    .build_ix()
    .await?;

§Step 3: Place an Order

let request = client.orders().limit_order().await
    .maker(keypair.pubkey())
    .bid()
    .price("0.55")
    .size("1")
    .sign(&keypair, &orderbook)?;
let order = client.orders().submit(&request).await?;

§Step 4: Monitor

let open = client
    .orders()
    .get_user_orders(&keypair.pubkey().to_string(), Some(50), None)
    .await?;
let mut ws = client.ws_native();
ws.connect().await?;
ws.subscribe(SubscribeParams::Books {
    orderbook_ids: vec![orderbook.orderbook_id.clone()],
})?;
ws.subscribe(SubscribeParams::User {
    wallet_address: keypair.pubkey().into(),
})?;

§Step 5: Cancel an Order

let cancel = CancelBody::signed(order.order_hash.clone(), keypair.pubkey().into(), &keypair);
client.orders().cancel(&cancel).await?;

§Step 6: Exit a Position

// sign_and_submit builds the tx, signs it using the client's signing strategy, and submits
let tx_hash = client.positions().merge()
    .user(keypair.pubkey())
    .market(&market)
    .mint(deposit_mint)
    .amount(1_000_000)
    .sign_and_submit()
    .await?;

§Step 7: Withdraw

let withdraw_ix = client.positions().withdraw().await
    .user(keypair.pubkey())
    .mint(deposit_mint)
    .amount(1_000_000)
    .build_ix()
    .await?;

§Authentication

Authentication is only required for user-specific endpoints. Authentication is session-based using ED25519 signed messages. The flow is: request a nonce, sign it with your wallet, and exchange it for a session token.

§Environment Configuration

The SDK defaults to the production environment. Use LightconeEnv to target a different deployment:

// Production (default — no .env() call needed)
let client = LightconeClient::builder().build()?;

// Staging
let client = LightconeClient::builder()
    .env(LightconeEnv::Staging)
    .build()?;

// Local development
let client = LightconeClient::builder()
    .env(LightconeEnv::Local)
    .build()?;

Each environment configures the API URL, WebSocket URL, Solana RPC URL, and on-chain program ID automatically. Individual URL overrides (.base_url(), .ws_url(), .rpc_url()) take precedence when called after .env().

§Examples

All examples are runnable with cargo run --example <name> --features native. Examples default to the production environment and read the wallet keypair from ~/.config/solana/id.json.

§Setup & Authentication

ExampleDescription
loginFull auth lifecycle: sign message, login, check session, logout

§Market Discovery & Data

ExampleDescription
marketsFeatured markets, paginated listing, fetch by pubkey, search
orderbookFetch orderbook depth (bids/asks) and decimal precision metadata
tradesRecent trade history with cursor-based pagination
price_historyHistorical candlestick data (OHLCV) at various resolutions
positionsUser positions across all markets and per-market

§Placing Orders

ExampleDescription
submit_orderLimit order via client.orders().limit_order() with human-readable price/size, auto-scaling, and fill tracking

§Cancelling Orders

ExampleDescription
cancel_orderCancel a single order by hash and cancel all orders in an orderbook
user_ordersFetch open orders for an authenticated user

§On-Chain Operations

ExampleDescription
read_onchainRead exchange state, market state, user nonce, and PDA derivations via RPC
onchain_transactionsBuild, sign, and submit mint/merge complete set and increment nonce on-chain
global_deposit_withdrawalInit position tokens, deposit to global pool, move capital into a market, extend an existing ALT, and withdraw from global

§WebSocket Streaming

ExampleDescription
ws_book_and_tradesLive orderbook depth with OrderbookSnapshot state + rolling TradeHistory buffer
ws_ticker_and_pricesBest bid/ask ticker + price history candles with PriceHistoryState
ws_user_and_marketAuthenticated user stream (orders, balances) + market lifecycle events

§Error Handling

All SDK operations return Result<T, SdkError>:

VariantWhen
SdkError::Http(HttpError)REST request failures
SdkError::Ws(WsError)WebSocket connection/protocol errors
SdkError::Auth(AuthError)Authentication failures
SdkError::Validation(String)Domain type conversion failures
SdkError::Serde(serde_json::Error)Serialization errors
SdkError::MissingMarketContext(string)Market context not provided for operation requiring DepositSource::Market
SdkError::Signing(String)Signing operation failures
SdkError::UserCancelledUser cancelled wallet signing prompt
SdkError::ApiRejected(ApiRejectedDetails)Backend rejected the request (see API Rejections)
SdkError::Program(program::SdkError)On-chain program errors (RPC, account parsing)
SdkError::Other(String)Catch-all

§API Rejections

When the backend rejects a request (insufficient balance, expired order, etc.), the SDK returns SdkError::ApiRejected(details) where details is an ApiRejectedDetails containing:

FieldTypeDescription
reasonStringHuman-readable error message
rejection_codeOption<RejectionCode>Machine-readable rejection code (see below)
error_codeOption<String>API-level error code (e.g. "NOT_FOUND", "INVALID_ARGUMENT")
error_log_idOption<String>Backend support correlation ID (LCERR_*)
request_idOption<String>SDK-generated x-request-id for cross-service tracing

Display formats all present fields as a multi-line report. Use .to_string() for logging or clipboard.

§RejectionCode

Machine-readable rejection codes with a human-readable .label() method. Unrecognized codes from the backend are captured as Unknown(String) for forward compatibility.

VariantLabelWhen
InsufficientBalance“Insufficient Balance”Not enough funds to fill the order
Expired“Expired”Order expiration time has passed
NonceMismatch“Nonce Mismatch”Order nonce doesn’t match current user nonce
SelfTrade“Self Trade”Order would match against the maker’s own order
MarketInactive“Market Inactive”Market is not accepting orders
BelowMinOrderSize“Below Min Order Size”Order size is below the minimum
InvalidNonce“Invalid Nonce”Nonce is invalid
BroadcastFailure“Broadcast Failure”Failed to broadcast to the network
OrderNotFound“Order Not Found”Order does not exist
NotOrderMaker“Not Order Maker”Caller is not the order maker
OrderAlreadyFilled“Order Already Filled”Order has already been fully filled
OrderAlreadyCancelled“Order Already Cancelled”Order was already cancelled
Unknown(String)(raw code)Unrecognized code (forward compatible)
match client.orders().submit(&request).await {
    Ok(response) => println!("Order placed: {}", response.order_hash),
    Err(SdkError::ApiRejected(details)) => {
        if let Some(code) = &details.rejection_code {
            println!("Rejected ({}): {}", code.label(), details.reason);
        }
        if let Some(log_id) = &details.error_log_id {
            println!("Support code: {}", log_id);
        }
    }
    Err(other) => eprintln!("Error: {}", other),
}

§Request Correlation

The SDK generates a UUID v4 x-request-id header on every HTTP request. On rejection, this ID is attached to ApiRejectedDetails.request_id for cross-service tracing. The same ID is sent to the backend for correlation in logs and error events.

HttpError variants:

VariantMeaning
Reqwest(reqwest::Error)Network/transport failure
ServerError { status, body }Non-2xx response from the backend
RateLimited { retry_after_ms }429 - back off and retry
Unauthorized401 - session expired or missing
NotFound(String)404 - resource not found
BadRequest(String)400 - invalid request
TimeoutRequest timed out
MaxRetriesExceeded { attempts, last_error }All retry attempts exhausted

§Retry Strategy

  • GET requests: RetryPolicy::Idempotent - retries on transport failures and 502/503/504, backs off on 429 with exponential backoff + jitter.
  • POST requests (order submit, cancel, auth): RetryPolicy::None - no automatic retry. Non-idempotent actions are never retried to prevent duplicate side effects.
  • Customizable per-call with RetryPolicy::Custom(RetryConfig { .. }).

Modules§

auth
Authentication: message generation, credentials, login/logout.
client
LightconeClient — the primary entry point. High-level client — LightconeClient with nested sub-client accessors.
domain
Domain modules (vertical slices): types, wire types, conversions, state. Domain modules organized as vertical slices.
env
Environment configuration: deployment targets, URLs, and program IDs. Environment configuration for the Lightcone SDK.
error
Unified SDK error types. Unified SDK error types.
http
HTTP client with retry policies. HTTP client layer — LightconeHttp with per-endpoint retry policies.
network
Network configuration (re-exports from [env]). Network configuration.
prelude
privy
Privy embedded wallet RPC operations. Privy sub-client — embedded wallet RPC operations.
program
On-chain program interaction: instructions, orders, PDAs, accounts.
rpc
RPC sub-client: PDA helpers, account fetchers, blockhash access. RPC sub-client — exchange-level on-chain fetchers, global deposit helpers, and blockhash access.
shared
Shared newtypes used across all domains. Shared newtypes and utilities used across all domain modules.
ws
WebSocket client: messages, subscriptions, events.