o2-sdk 0.2.0

Rust SDK for the O2 Exchange — a fully on-chain order book DEX on the Fuel Network
Documentation
use o2_sdk::crypto::*;
/// Taker bot example: monitors depth via WebSocket and executes when price
/// crosses a configurable threshold.
use o2_sdk::*;
use tokio_stream::StreamExt;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let buy_below_price: UnsignedDecimal = "0.04".parse()?;
    let max_quantity = "50";

    let mut client = O2Client::new(Network::Testnet);

    // Setup
    let wallet = client.generate_wallet()?;
    println!("Owner: {}", to_hex_string(&wallet.b256_address));

    let account = client.setup_account(&wallet).await?;
    let trade_account_id = account.trade_account_id.unwrap();
    println!("Trade account: {trade_account_id}");

    // Wait for faucet cooldown and mint again
    println!("Waiting for faucet cooldown...");
    tokio::time::sleep(std::time::Duration::from_secs(65)).await;
    let _ = client.setup_account(&wallet).await;

    let markets = client.get_markets().await?;
    let market_pair = markets[0].symbol_pair();
    let market = &markets[0];
    println!("Monitoring: {market_pair}");

    let mut session = client
        .create_session(
            &wallet,
            &[market_pair.as_str()],
            std::time::Duration::from_secs(30 * 24 * 3600),
        )
        .await?;
    println!("Session created");

    // Connect to WebSocket for real-time depth
    let mut depth_stream = client.stream_depth(market.market_id.as_str(), 1).await?;

    println!("Listening for depth updates (buy when ask <= {buy_below_price})...");

    while let Some(Ok(update)) = depth_stream.next().await {
        // Get best ask from snapshot or update
        let sells = update
            .view
            .as_ref()
            .map(|v| &v.asks)
            .or_else(|| update.changes.as_ref().map(|c| &c.asks));

        if let Some(sell_levels) = sells {
            if let Some(best_ask) = sell_levels.first() {
                let ask_price: u64 = best_ask.price;
                if ask_price == 0 {
                    continue;
                }
                let ask_human = market.format_price(ask_price);

                if ask_human <= buy_below_price {
                    println!("Target price hit! Best ask: {ask_human}");

                    // Use a price slightly above the ask (0.5% slippage)
                    let slippage_factor: UnsignedDecimal = "1.005".parse()?;
                    let taker_price = ask_human * slippage_factor;
                    let price = match market.price_from_decimal(taker_price) {
                        Ok(v) => v,
                        Err(e) => {
                            eprintln!("Skipping order due to invalid taker price: {e}");
                            continue;
                        }
                    };

                    let result = client
                        .create_order(
                            &mut session,
                            market_pair.as_str(),
                            Side::Buy,
                            price,
                            max_quantity,
                            OrderType::Spot,
                            true,
                            true,
                        )
                        .await;

                    match result {
                        Ok(resp) if resp.is_success() => {
                            println!("Order placed! tx: {}", resp.tx_id.as_deref().unwrap_or("?"));
                        }
                        Ok(resp) => {
                            eprintln!("Order failed: {:?}", resp.message);
                        }
                        Err(e) => {
                            eprintln!("Order error: {e}");
                            let _ = client.refresh_nonce(&mut session).await;
                        }
                    }
                } else {
                    println!("Best ask: {ask_human} (waiting for <= {buy_below_price})");
                }
            }
        }
    }

    Ok(())
}