limitless-exchange-rust-sdk 1.0.13

Rust SDK for Limitless Exchange CLOB and NegRisk trading
Documentation
# Limitless Exchange Rust SDK

**v1.0.13** | Rust SDK parity with the existing Limitless SDK surface

Rust SDK for interacting with the Limitless Exchange API.

This crate is a parity-driven Rust port of the existing Limitless SDK surface. The current implementation includes:

- shared HTTP client with API key, identity-header, and HMAC auth support
- typed API errors and retry helpers
- root `Client`
- markets, portfolio, and market-pages services
- partner api-token, partner-account, and server-wallet services
- order builder, validator, EIP-712 signer, and order client
- delegated-order service
- websocket types and socket.io client surface

**USE AT YOUR OWN RISK**

This SDK is provided "as-is" without any warranties or guarantees. Trading on prediction markets involves financial risk. By using this SDK, you acknowledge that:

- You are responsible for testing the SDK thoroughly before using it in production
- The SDK authors are not liable for any financial losses or damages
- You should review and understand the code before executing any trades
- It is recommended to test all functionality on testnet or with small amounts first
- The SDK may contain bugs or unexpected behavior despite best efforts

**ALWAYS TEST BEFORE USING IN PRODUCTION WITH REAL FUNDS**

For production use, we strongly recommend:

1. Running comprehensive tests with your specific use case
2. Starting with small transaction amounts
3. Monitoring all transactions carefully
4. Having proper error handling and recovery mechanisms

## Geographic Restrictions

**Important**: Limitless restricts order placement from US locations due to regulatory requirements and compliance with international sanctions. Before placing orders, builders should verify their location complies with applicable regulations.

## Status

This is the first full-surface parity pass. The crate is implemented against the Go SDK shape and verified locally with:

- `cargo fmt`
- `cargo check --examples`
- `cargo test`

## Installation

```toml
[dependencies]
limitless-exchange-rust-sdk = "1.0.13"
```

## Authentication Modes

- Public read-only endpoints: no authentication required. Use these for active markets, market pages, and orderbooks.
- API key authentication: required for portfolio and standard order-placement flows.
- HMAC-scoped authentication: used for partner/delegated/server-wallet flows and can also authenticate websocket position streams.

The SDK reads `LIMITLESS_API_KEY` automatically when present, or you can configure credentials explicitly with `Client::builder()`.

## Quick Start

### Public Market Data

```rust
use limitless_exchange_rust_sdk::{ActiveMarketsParams, ActiveMarketsSortBy, Client};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let sdk = Client::new()?;

    let markets = sdk
        .markets
        .get_active_markets(Some(&ActiveMarketsParams {
            limit: Some(5),
            page: None,
            sort_by: Some(ActiveMarketsSortBy::Newest),
        }))
        .await?;

    println!("Found {} markets", markets.data.len());
    Ok(())
}
```

### Authenticated Portfolio Access

```rust
use std::env;

use limitless_exchange_rust_sdk::Client;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let sdk = Client::from_http_client(
        Client::builder()
            .api_key(env::var("LIMITLESS_API_KEY")?)
            .build()?,
    )?;

    let positions = sdk.portfolio.get_positions().await?;
    println!("CLOB positions: {}", positions.clob.len());

    let profile = sdk.portfolio.get_current_profile().await?;
    println!("Profile: {} {}", profile.id, profile.account);

    let history = sdk.portfolio.get_user_history(None, Some(20)).await?;
    println!("History entries: {}", history.data.len());

    if let Some(next_cursor) = history.next_cursor.as_deref() {
        let next_page = sdk
            .portfolio
            .get_user_history(Some(next_cursor), Some(20))
            .await?;
        println!("Next page entries: {}", next_page.data.len());
    }

    Ok(())
}
```

### Signed Order Placement

```rust
use std::env;

use limitless_exchange_rust_sdk::{Client, GtcOrderArgs, OrderArgs, OrderType, Side};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::from_http_client(
        Client::builder()
            .api_key(env::var("LIMITLESS_API_KEY")?)
            .build()?
    )?;

    let market = client.markets.get_market("btc-above-150k-by-jun-2026").await?;
    println!("market: {}", market.title);

    let private_key = env::var("PRIVATE_KEY")?;
    let order_client = client.new_order_client(
        &private_key,
        None,
    )?;

    let order = order_client
        .create_order(limitless_exchange_rust_sdk::CreateOrderParams {
            order_type: OrderType::Gtc,
            market_slug: market.slug.clone(),
            args: OrderArgs::from(GtcOrderArgs {
                token_id: market.outcomes[0].token_id.clone(),
                side: Side::Buy,
                price: 0.51,
                size: 10.0,
                expiration: None,
                nonce: None,
                taker: None,
                post_only: false,
            }),
        })
        .await?;

    println!("order id: {}", order.order.id);
    Ok(())
}
```

### Optional Receive Window

Order creation can opt into API receive-window freshness checks with `ReceiveWindowOptions`. These values are sent as top-level `POST /orders` fields named `timestamp` and `recvWindow`; they are not included in the signed EIP-712 order payload. Existing `create_order` calls are unchanged and omit both fields.

```rust
use limitless_exchange_rust_sdk::{
    CreateOrderParams, GtcOrderArgs, OrderArgs, OrderType, ReceiveWindowOptions, Side,
};

let order = order_client
    .create_order_with_receive_window(
        CreateOrderParams {
            order_type: OrderType::Gtc,
            market_slug: market.slug.clone(),
            args: OrderArgs::from(GtcOrderArgs {
                token_id: market.outcomes[0].token_id.clone(),
                side: Side::Buy,
                price: 0.51,
                size: 10.0,
                expiration: None,
                nonce: None,
                taker: None,
                post_only: false,
            }),
        },
        ReceiveWindowOptions {
            timestamp: None,
            recv_window: Some(1500),
        },
    )
    .await?;
```

`recv_window` must be between `1` and `10000` milliseconds. When `recv_window` is supplied without `timestamp`, the SDK stamps the current Unix time in milliseconds. Keep trading hosts NTP-synced; server clock skew tolerance is about one second. Do not retry a `425 Too Early` with the same payload; build a fresh order instead.

## Workflow Guide

- Public market discovery: [examples/active_markets.rs]examples/active_markets.rs
- Custom client builder and logging: [examples/custom_client.rs]examples/custom_client.rs
- Market-page discovery and filtered browsing: [examples/market_pages.rs]examples/market_pages.rs
- Portfolio and cursor-based history: [examples/portfolio.rs]examples/portfolio.rs
- User-order retrieval and market cancel-all: [examples/user_orders.rs]examples/user_orders.rs
- Signed CLOB orders:
  - GTC: [examples/clob_gtc_order.rs]examples/clob_gtc_order.rs
  - FAK: [examples/clob_fak_order.rs]examples/clob_fak_order.rs
  - FOK: [examples/clob_fok_order.rs]examples/clob_fok_order.rs
- NegRisk order flow: [examples/negrisk_order.rs]examples/negrisk_order.rs
- Delegated partner flows:
  - delegated order: [examples/delegated_order.rs]examples/delegated_order.rs
  - delegated FOK order: [examples/delegated_fok_order.rs]examples/delegated_fok_order.rs
- Partner allowance recovery: [examples/partner_account_allowances.rs]examples/partner_account_allowances.rs
- API-token revoke flow: [examples/api_token_revoke.rs]examples/api_token_revoke.rs
- Server-wallet redeem/withdraw flow: [examples/server_wallet_redeem_withdraw.rs]examples/server_wallet_redeem_withdraw.rs
- WebSocket subscriptions:
  - orderbook: [examples/websocket_orderbook.rs]examples/websocket_orderbook.rs
  - positions and transactions: [examples/websocket_positions.rs]examples/websocket_positions.rs

### Partner Server-Wallet Allowances

Use `partner_accounts.list_accounts` to list partner-owned sub-accounts, or pass `account` to recover a specific child profile by address. This endpoint requires scoped HMAC credentials with the `account_creation` scope. The API caps `limit` at 25 and rejects `x-on-behalf-of` on this route.

```rust
use limitless_exchange_rust_sdk::{Client, HmacCredentials, ListPartnerAccountsParams};

let sdk = Client::from_http_client(
    Client::builder()
        .hmac_credentials(HmacCredentials {
            token_id: std::env::var("LIMITLESS_API_TOKEN_ID")?,
            secret: std::env::var("LIMITLESS_API_TOKEN_SECRET")?,
        })
        .build()?,
)?;

let accounts = sdk
    .partner_accounts
    .list_accounts(&ListPartnerAccountsParams {
        limit: Some(25),
        page: Some(1),
        ..Default::default()
    })
    .await?;

let recovered = sdk
    .partner_accounts
    .list_accounts(&ListPartnerAccountsParams {
        account: Some("0xChildAccount".to_string()),
        limit: Some(25),
        page: Some(1),
    })
    .await?;

println!("accounts: {} recovered: {}", accounts.data.len(), recovered.data.len());
```

Use `partner_accounts.check_allowances(profile_id)` and `partner_accounts.retry_allowances(profile_id)` only for partner child profiles created with `create_server_wallet = true`. These endpoints require scoped HMAC credentials derived with `SCOPE_ACCOUNT_CREATION` and `SCOPE_DELEGATED_SIGNING`.

```rust
use limitless_exchange_rust_sdk::{Client, HmacCredentials};

let sdk = Client::from_http_client(
    Client::builder()
        .hmac_credentials(HmacCredentials {
            token_id: std::env::var("LIMITLESS_API_TOKEN_ID")?,
            secret: std::env::var("LIMITLESS_API_TOKEN_SECRET")?,
        })
        .build()?,
)?;

let profile_id = 12345;
let mut allowances = sdk.partner_accounts.check_allowances(profile_id).await?;
if !allowances.ready {
    // Retry re-checks live chain state and submits only targets still missing.
    // A returned "submitted" status means this request submitted a sponsored tx/user operation.
    allowances = sdk.partner_accounts.retry_allowances(profile_id).await?;
}

println!("allowance ready: {}", allowances.ready);
```

Poll `check_allowances` first. If `ready` is false and one or more targets are `missing` or `failed` with `retryable = true`, call `retry_allowances`, then poll `check_allowances` again after a short delay. Retry `429` and `409` responses are returned as `LimitlessError::Api`; inspect `err.status`, and for `429` read `retryAfterSeconds` from `err.data`.

### Server Wallet Redeem & Withdraw

Use `server_wallets.redeem_positions` and `server_wallets.withdraw` only for server-managed wallets created in delegated-signing partner flows with `create_server_wallet = true`.

- `redeem_positions` calls `POST /portfolio/redeem`
- `withdraw` calls `POST /portfolio/withdraw`
- both operations require HMAC-scoped API-token auth
- `withdraw` also requires the `withdrawal` scope
- set `on_behalf_of` to the delegated child-profile id when withdrawing child server-wallet funds
- omit `on_behalf_of` only when withdrawing the authenticated caller's own server wallet to an explicit `destination`
- omit `destination` to use the API default: authenticated partner smart wallet when present, otherwise authenticated partner account
- pass `destination` to withdraw directly to the authenticated partner account, authenticated partner smart wallet, or an active withdrawal address allowlisted on the authenticated partner profile
- `partner_accounts.add_withdrawal_address` and `partner_accounts.delete_withdrawal_address` manage the allowlist with Privy identity-token auth; API-token auth is not used for those allowlist endpoints

```rust
use limitless_exchange_rust_sdk::{
    Client, HmacCredentials, PartnerWithdrawalAddressInput, WithdrawServerWalletParams,
};

let sdk = Client::from_http_client(
    Client::builder()
        .hmac_credentials(HmacCredentials {
            token_id: std::env::var("LIMITLESS_API_TOKEN_ID")?,
            secret: std::env::var("LIMITLESS_API_TOKEN_SECRET")?,
        })
        .build()?,
)?;

let identity_token = std::env::var("LIMITLESS_IDENTITY_TOKEN")?;
let treasury_address = "0x0F3262730c909408042F9Da345a916dc0e1F9787";

sdk.partner_accounts
    .add_withdrawal_address(
        &identity_token,
        &PartnerWithdrawalAddressInput {
            address: treasury_address.to_string(),
            label: Some("treasury".to_string()),
        },
    )
    .await?;

let child_withdraw = sdk
    .server_wallets
    .withdraw(&WithdrawServerWalletParams {
        amount: "5000000".to_string(),
        on_behalf_of: Some(352),
        token: None,
        destination: Some(treasury_address.to_string()),
    })
    .await?;

let own_wallet_withdraw = sdk
    .server_wallets
    .withdraw(&WithdrawServerWalletParams {
        amount: "5000000".to_string(),
        on_behalf_of: None,
        token: None,
        destination: Some(treasury_address.to_string()),
    })
    .await?;

println!(
    "withdraws: {} {}",
    child_withdraw.envelope.transaction_id, own_wallet_withdraw.envelope.transaction_id
);
```