# Lightcone SDK
Rust SDK for the Lightcone impact market protocol on Solana.
## Table of Contents
- [Installation](#installation)
- [Feature Flags](#feature-flags)
- [Quick Start](#quick-start)
- [Start Trading](#start-trading)
- [Step 1: Find a Market](#step-1-find-a-market)
- [Step 2: Deposit Collateral](#step-2-deposit-collateral)
- [Step 3: Place an Order](#step-3-place-an-order)
- [Step 4: Monitor](#step-4-monitor)
- [Step 5: Cancel an Order](#step-5-cancel-an-order)
- [Step 6: Exit a Position](#step-6-exit-a-position)
- [Step 7: Withdraw](#step-7-withdraw)
- [Examples](#examples)
- [Authentication](#authentication)
- [Error Handling](#error-handling)
- [Retry Strategy](#retry-strategy)
## Installation
Add to your `Cargo.toml`:
```toml
[dependencies]
lightcone = { version = "0.4.1", features = ["native"] }
```
For browser/WASM targets:
```toml
[dependencies]
lightcone = { version = "0.4.1", features = ["wasm"] }
```
## Feature Flags
| **`native`** | `http` + `native-auth` + `ws-native` + `solana-rpc` | **Market makers, bots, CLI tools** |
| **`wasm`** | `http` + `ws-wasm` | **Browser applications** |
## Quick Start
```rust
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
```rust
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
```rust
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
```rust
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
```rust
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
```rust
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
```rust
let cancel = CancelBody::signed(order.order_hash.clone(), keypair.pubkey().into(), &keypair);
client.orders().cancel(&cancel).await?;
```
### Step 6: Exit a Position
```rust
// 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
```rust
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:
```rust
// 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
| [`login`](examples/login.rs) | Full auth lifecycle: sign message, login, check session, logout |
### Market Discovery & Data
| [`markets`](examples/markets.rs) | Featured markets, paginated listing, fetch by pubkey, search |
| [`orderbook`](examples/orderbook.rs) | Fetch orderbook depth (bids/asks) and decimal precision metadata |
| [`trades`](examples/trades.rs) | Recent trade history with cursor-based pagination |
| [`price_history`](examples/price_history.rs) | Historical candlestick data (OHLCV) at various resolutions |
| [`positions`](examples/positions.rs) | User positions across all markets and per-market |
### Placing Orders
| [`submit_order`](examples/submit_order.rs) | Limit order via `client.orders().limit_order()` with human-readable price/size, auto-scaling, and fill tracking |
### Cancelling Orders
| [`cancel_order`](examples/cancel_order.rs) | Cancel a single order by hash and cancel all orders in an orderbook |
| [`user_orders`](examples/user_orders.rs) | Fetch open orders for an authenticated user |
### On-Chain Operations
| [`read_onchain`](examples/read_onchain.rs) | Read exchange state, market state, user nonce, and PDA derivations via RPC |
| [`onchain_transactions`](examples/onchain_transactions.rs) | Build, sign, and submit mint/merge complete set and increment nonce on-chain |
| [`global_deposit_withdrawal`](examples/global_deposit_withdrawal.rs) | Init position tokens, deposit to global pool, move capital into a market, extend an existing ALT, and withdraw from global |
### WebSocket Streaming
| [`ws_book_and_trades`](examples/ws_book_and_trades.rs) | Live orderbook depth with `OrderbookSnapshot` state + rolling `TradeHistory` buffer |
| [`ws_ticker_and_prices`](examples/ws_ticker_and_prices.rs) | Best bid/ask ticker + price history candles with `PriceHistoryState` |
| [`ws_user_and_market`](examples/ws_user_and_market.rs) | Authenticated user stream (orders, balances) + market lifecycle events |
## Error Handling
All SDK operations return `Result<T, SdkError>`:
| `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::Program(program::SdkError)` | On-chain program errors (RPC, account parsing) |
| `SdkError::Other(String)` | Catch-all |
`HttpError` variants:
| `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 { .. })`.