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:
[]
= { = "0.4.1", = ["native"] }
For browser/WASM targets:
[]
= { = "0.4.1", = ["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 *;
use sign_login_message;
use Keypair;
async
Start Trading
use *;
use read_keypair_file;
use Signer;
// Defaults to Prod. Use .env(LightconeEnv::Staging) for staging.
let client = builder
.deposit_source
.build?;
let keypair = read_keypair_file?;
Step 1: Find a Market
let market = client.markets.get.await?.markets.into_iter.next.unwrap;
let orderbook = market
.orderbook_pairs
.iter
.find
.or_else
.expect;
Step 2: Deposit Collateral
let deposit_mint = market.deposit_assets.pubkey.to_pubkey?;
let deposit_ix = client.positions.deposit.await
.user
.mint
.amount
.build_ix
.await?;
Step 3: Place an Order
let request = client.orders.limit_order.await
.maker
.bid
.price
.size
.sign?;
let order = client.orders.submit.await?;
Step 4: Monitor
let open = client
.orders
.get_user_orders
.await?;
let mut ws = client.ws_native;
ws.connect.await?;
ws.subscribe?;
ws.subscribe?;
Step 5: Cancel an Order
let cancel = signed;
client.orders.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
.market
.mint
.amount
.sign_and_submit
.await?;
Step 7: Withdraw
let withdraw_ix = client.positions.withdraw.await
.user
.mint
.amount
.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 = builder.build?;
// Staging
let client = builder
.env
.build?;
// Local development
let client = builder
.env
.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.await
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 { .. }).