Example Bots

The SDK ships with runnable examples and patterns for bots and integrations.

read_markets — Read-Only Market Discovery

Connects to the indexer and chain, fetches all markets, and displays the orderbook for the first active market. No wallet required.

cargo run --example read_markets
use strike_sdk::prelude::*;

#[tokio::main]
async fn main() -> Result<()> {
    tracing_subscriber::fmt::init();

    let client = StrikeClient::new(StrikeConfig::bsc_mainnet()).build()?;

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

    for market in &markets {
        println!(
            "  factory {} | orderbook {:?} | status: {} | expiry: {} | batch_interval: {}s",
            market.factory_market_id,
            market.orderbook_market_id,
            market.status,
            market.expiry_time,
            market.batch_interval,
        );
    }

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

    // Get orderbook for first active market using the tradable OrderBook ID
    let active_markets: Vec<_> = markets.iter().filter(|m| m.status == "active").collect();
    if let Some(market) = active_markets.first() {
        let market_id = market.tradable_market_id()?;
        let ob = client.indexer().get_orderbook(market_id).await?;
        println!("\norderbook for factory market {} (ob {}):", market.factory_market_id, market_id);
        for level in &ob.bids {
            println!("  bid: tick {} | {} lots", level.tick, level.lots);
        }
        for level in &ob.asks {
            println!("  ask: tick {} | {} lots", level.tick, level.lots);
        }
    }

    Ok(())
}

What it demonstrates: Client setup in read-only mode, indexer queries, on-chain market reads.

redeem_backlog_check — Find Redeemable Positions Before Redemption

This is the concise discovery flow for redemption bots:

  1. query the indexer backlog with get_redeemable_positions(address)

  2. pull the factory market ID from each normalized entry

  3. pass that factory market ID into on-chain redemption

The SDK normalizes legacy and v1 redeemable payload variants here, so factory_market_id() and related accessors remain the stable interface.

place_orders — Full Trading Lifecycle

Connects with a wallet, approves USDT, finds an active market, places a bid and ask, then cancels both.

What it demonstrates: Wallet setup, USDT approval, order placement and cancellation.

active_market_quote — Find the Active Market and Quote It

This example shows a clean two-sided quoting flow:

  1. fetch active markets from the indexer

  2. pick a market

  3. read the current orderbook

  4. derive bid/ask quote ticks from the book

  5. approve USDT if needed

  6. place both sides in one transaction

What it demonstrates: active market discovery, orderbook-based quote selection, paired order placement.

atomic_requote — Replace Existing Quotes in One Transaction

This example shows how to cancel stale quotes and place fresh ones atomically using replace().

Use this when you already have resting quotes on a market and want to move them without creating a cancel/place gap.

What it demonstrates: atomic cancel-and-place, quote refresh without a gap, and practical use of replace().

track_order_lifecycle — Accepted/Live vs Filled

Minimal bot-flow example for the most important execution-state distinction:

  • successful place_market() = accepted/live

  • later OrderSettled = filled

  • OrderCancelled = explicitly cancelled / cleaned up

  • GtcAutoCancelled = auto-cancelled by batch auction

This example keeps local metadata keyed by returned order_id, which is the same core pattern used in the real market maker. That local tracking matters because OrderSettled currently includes order_id, owner, and filled_lots, but not market_id or side.

What it demonstrates: correct bot execution-state handling, a tiny local lifecycle enum, local order_id tracking, and why accepted/live must be treated separately from filled.

Operational notes:

  • the SDK nonce-manager feature is enabled by default. For bots, keep one serialized tx pipeline per wallet and avoid concurrent place/cancel/replace sends from the same wallet.

  • if no terminal event arrives before your timeout, mark the order as needing reconciliation and fall back to scan_orders() / indexer positions instead of guessing.

  • OrderCancelled is best treated as part of recovery/reconciliation as well; the live WSS stream is centered on settlement/batch events, so bots should always have a recovery path after reconnect gaps.

stream_events — Event-Driven Architecture

Subscribes to WSS events and prints market creations, batch clearings, and settlements.

What it demonstrates: Event streaming, pattern matching on StrikeEvent, read-only WSS subscription.

simple_bot — Minimal Market Maker Skeleton

Event-driven market maker that demonstrates real bot patterns from the Strike MM. On startup it bootstraps from the indexer's active markets so it can quote immediately, then continues reacting to live events. Quotes are placed around the orderbook midpoint with a fixed spread, requotes atomically via replaceOrders, tracks fills, and cancels all orders on shutdown.

Key patterns demonstrated:

Pattern
Why it matters

init_nonce_sender()

Prevents nonce-too-low errors under rapid sends

scan_orders() startup recovery

Cancels stale orders from previous runs

get_active_markets() bootstrap

Quotes current active markets immediately on startup

replace() for requoting

Atomic cancel + place — zero empty-book time

tokio::select! event loop

React to events, handle graceful shutdown

Position tracking via OrderSettled

Know your net exposure per market

What it demonstrates: Nonce management, event streaming, order placement, atomic requoting, startup recovery, graceful shutdown.

Tips for Building a Real Bot

  • Pricing: Use a Pyth price feed to compute fair value instead of a fixed mid. The strike price and expiry are in MarketCreated.

  • Position tracking: Use scan_orders() on startup to recover open orders, then track fills via OrderSettled events.

  • Risk management: Track net position per market. Consider maximum exposure limits and position sizing.

  • Requoting: Use replace() to atomically cancel stale quotes and place new ones, avoiding temporary unhedged exposure.

  • Nonce management: Enable init_nonce_sender() for rapid transaction sending without nonce collisions.

  • Reconnection: EventStream auto-reconnects on WSS failures, but you may miss events during the gap. Periodically reconcile state via the indexer or scan_orders().

Last updated