lightcone 0.4.4

Rust SDK for the Lightcone Protocol — unified native + WASM client
# 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

| 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

```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

| Example | Description |
|---------|-------------|
| [`login`]examples/login.rs | Full auth lifecycle: sign message, login, check session, logout |

### Market Discovery & Data

| Example | Description |
|---------|-------------|
| [`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

| Example | Description |
|---------|-------------|
| [`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

| Example | Description |
|---------|-------------|
| [`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

| Example | Description |
|---------|-------------|
| [`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

| Example | Description |
|---------|-------------|
| [`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>`:

| 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::Program(program::SdkError)` | On-chain program errors (RPC, account parsing) |
| `SdkError::Other(String)` | Catch-all |

`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 { .. })`.