strike-sdk 0.2.4

Rust SDK for Strike prediction markets on BNB Chain
Documentation
# strike-sdk

Rust SDK for [Strike](https://github.com/ayazabbas/strike) prediction markets on BNB Chain.

## Installation

```toml
[dependencies]
strike-sdk = "0.2"
```

Or via cargo:

```bash
cargo add strike-sdk
```

## Quick Start

### Read-only (no wallet)

```rust
use strike_sdk::prelude::*;

#[tokio::main]
async fn main() -> Result<()> {
    let client = StrikeClient::new(StrikeConfig::bsc_testnet()).build()?;

    // Fetch markets from indexer
    let markets = client.indexer().get_markets().await?;
    println!("found {} markets", markets.len());

    // Read on-chain state
    let count = client.markets().active_market_count().await?;
    println!("{count} active markets on-chain");

    Ok(())
}
```

### Trading (with wallet)

```rust
use strike_sdk::prelude::*;

#[tokio::main]
async fn main() -> Result<()> {
    let client = StrikeClient::new(StrikeConfig::bsc_testnet())
        .with_private_key("0x...")
        .build()?;

    // Approve USDT (one-time, idempotent)
    client.vault().approve_usdt().await?;

    // Fetch a market from the indexer and trade using its tradable orderbook ID
    let market = client
        .indexer()
        .get_active_markets()
        .await?
        .into_iter()
        .next()
        .expect("no active markets");

    let orders = client
        .orders()
        .place_market(&market, &[
            OrderParam::bid(50, 1000),
            OrderParam::ask(60, 1000),
        ])
        .await?;

    // Cancel all placed orders
    let ids: Vec<_> = orders.iter().map(|o| o.order_id).collect();
    client.orders().cancel(&ids).await?;

    Ok(())
}
```

### Atomic Replace (cancel + place in one tx)

```rust
let new_orders = client.orders().replace(
    &old_order_ids,
    orderbook_market_id,
    &[OrderParam::bid(52, 1000), OrderParam::ask(58, 1000)],
).await?;
```

### Event Subscriptions

```rust
use futures_util::StreamExt;

let mut events = client.events().await?;
while let Some(event) = events.next().await {
    match event {
        StrikeEvent::MarketCreated { market_id, strike_price, .. } => {
            println!("new market {market_id} at strike {strike_price}");
        }
        StrikeEvent::BatchCleared { market_id, clearing_tick, matched_lots, .. } => {
            println!("batch cleared: market {market_id}, tick {clearing_tick}, {matched_lots} lots");
        }
        _ => {}
    }
}
```

## API Response Format (v0.2+)

As of v0.2, the Strike indexer returns paginated envelopes for list endpoints:

```json
{
  "data": [...],
  "meta": { "total": 441, "limit": 50, "offset": 0 }
}
```

The SDK handles both the new `{ data }` envelope and the legacy `{ markets }` format automatically — no changes needed in your code.

The `get_markets()` call now fetches active markets only by default (equivalent to `?status=active`). To fetch all markets use the underlying indexer client directly with query params.

Market IDs matter:

- `market.id` is retained as a backward-compatible alias of the factory market ID.
- `market.factory_market_id` is the canonical lifecycle/resolution ID.
- `market.orderbook_market_id` is the tradable ID for `OrderBook.placeOrders` and `replaceOrders`.
- `orders().place_market()` and `orders().replace_market()` fail closed if the indexer response does not include `orderbook_market_id`.

## Key Concepts

- **LOT_SIZE** = 1e16 wei ($0.01 per lot)
- **Ticks** are 1–99, representing $0.01–$0.99 probability
- **4-sided orderbook**: Bid, Ask, SellYes, SellNo
- **Order types**: GoodTilBatch (GTB) expires after one batch, GoodTilCancelled (GTC) rolls forward
- **Batch auctions**: orders are collected into batches and cleared atomically
- All fills pay the **clearing tick**, not the limit tick
- **Resting orders**: orders >20 ticks from last clearing tick are placed on a resting list (emit `OrderResting` instead of `OrderPlaced`). The SDK tracks both automatically.
- 1 YES + 1 NO = 1 USDT (always)

## Features

| Feature | Default | Description |
|---------|---------|-------------|
| `nonce-manager` | Yes | Shared nonce management for sequential TX sends |

Disable the nonce manager if you manage nonces yourself:

```toml
strike-sdk = { version = "0.2", default-features = false }
```

## Modules

| Module | Description |
|--------|-------------|
| `client` | `StrikeClient` builder (read-only and trading modes) |
| `chain::orders` | `placeOrders`, `replaceOrders`, `cancelOrders` |
| `chain::vault` | USDT approval, balance queries |
| `chain::redeem` | Outcome token redemption |
| `chain::tokens` | ERC-1155 outcome token helpers |
| `chain::markets` | On-chain market state reads |
| `events::subscribe` | WSS event stream with auto-reconnect |
| `events::scan` | Historical event scanning (chunked getLogs) |
| `indexer` | REST client: markets, positions, trades, stats (API v1) |
| `nonce` | `NonceSender` for sequential TX sends |

## AI Markets

Markets with `is_ai_market: true` are resolved by the Flap AI Oracle instead of Pyth price feeds. The `Market` struct includes:

- `is_ai_market` — whether this market uses AI resolution
- `ai_prompt` — the question sent to the LLM
- `ai_status` — resolution status: `pending`, `proposed`, `challenged`, `finalized`, `refunded`

### Checking AI Resolution

```rust
let market = client.indexer().get_market(920).await?;
if market.is_ai_market {
    println!("AI market: {}", market.ai_prompt.as_deref().unwrap_or(""));
    println!("Status: {:?}", market.ai_status);
}
```

### AI Resolution Details

Use the indexer endpoint to fetch full resolution data including the IPFS proof:

```rust
// GET /v1/markets/{id}/ai-resolution
let resolution = client.indexer().get_ai_resolution(920).await?;
println!("Choice: {} ({})", resolution.choice, resolution.choice_label);
println!("IPFS proof: {}", resolution.reasoning_url);
```

## Coming Soon

- Python SDK
- Full API v1 query builder