Expand description
§Lightcone SDK
Rust SDK for the Lightcone impact market protocol on Solana.
§Table of Contents
- Installation
- Feature Flags
- Quick Start
- Start Trading
- Examples
- Authentication
- Error Handling
- Retry Strategy
§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
| Feature | What it enables | Use case |
|---|---|---|
native | http + native-auth + ws-native + solana-rpc | Market makers, bots, CLI tools |
wasm | http + ws-wasm | Browser 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
| Example | Description |
|---|---|
login | Full auth lifecycle: sign message, login, check session, logout |
§Market Discovery & Data
| Example | Description |
|---|---|
markets | Featured markets, paginated listing, fetch by pubkey, search |
orderbook | Fetch orderbook depth (bids/asks) and decimal precision metadata |
trades | Recent trade history with cursor-based pagination |
price_history | Historical candlestick data (OHLCV) at various resolutions |
positions | User positions across all markets and per-market |
§Placing Orders
| Example | Description |
|---|---|
submit_order | Limit order via client.orders().limit_order() with human-readable price/size, auto-scaling, and fill tracking |
§Cancelling Orders
| Example | Description |
|---|---|
cancel_order | Cancel a single order by hash and cancel all orders in an orderbook |
user_orders | Fetch open orders for an authenticated user |
§On-Chain Operations
| Example | Description |
|---|---|
read_onchain | Read exchange state, market state, user nonce, and PDA derivations via RPC |
onchain_transactions | Build, sign, and submit mint/merge complete set and increment nonce on-chain |
global_deposit_withdrawal | Init position tokens, deposit to global pool, move capital into a market, extend an existing ALT, and withdraw from global |
§WebSocket Streaming
| Example | Description |
|---|---|
ws_book_and_trades | Live orderbook depth with OrderbookSnapshot state + rolling TradeHistory buffer |
ws_ticker_and_prices | Best bid/ask ticker + price history candles with PriceHistoryState |
ws_user_and_market | Authenticated user stream (orders, balances) + market lifecycle events |
§Error Handling
All SDK operations return Result<T, SdkError>:
| Variant | When |
|---|---|
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::UserCancelled | User 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:
| Field | Type | Description |
|---|---|---|
reason | String | Human-readable error message |
rejection_code | Option<RejectionCode> | Machine-readable rejection code (see below) |
error_code | Option<String> | API-level error code (e.g. "NOT_FOUND", "INVALID_ARGUMENT") |
error_log_id | Option<String> | Backend support correlation ID (LCERR_*) |
request_id | Option<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.
| Variant | Label | When |
|---|---|---|
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:
| Variant | Meaning |
|---|---|
Reqwest(reqwest::Error) | Network/transport failure |
ServerError { status, body } | Non-2xx response from the backend |
RateLimited { retry_after_ms } | 429 - back off and retry |
Unauthorized | 401 - session expired or missing |
NotFound(String) | 404 - resource not found |
BadRequest(String) | 400 - invalid request |
Timeout | Request 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 —LightconeClientwith 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 —
LightconeHttpwith 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.