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

[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

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

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

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.

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

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.

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.

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
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
);