ostium-rust-sdk 0.1.0

Rust SDK for interacting with the Ostium trading platform on Arbitrum
Documentation
//! Complete Unsigned Transaction Workflow Example
//!
//! This example demonstrates the complete workflow for generating unsigned transactions
//! for all trading operations, including proper validation and error handling.

use alloy::providers::ProviderBuilder;
use alloy_primitives::{aliases::U192, Address, U256};
use ostium_rust_sdk::{
    contracts::TradingContract,
    types::{OpenOrderType, Trade},
    Network, OstiumClient, UnsignedTransactionParams,
};
use std::str::FromStr;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::fmt::init();

    println!("🔥 Complete Unsigned Transaction Workflow");
    println!("==========================================");

    // Step 1: Create client without signer
    let client = OstiumClient::new(Network::Testnet).await?;
    println!("✅ Step 1: Created read-only client");
    assert!(!client.has_signer(), "Client should not have a signer");

    // Step 2: Setup contract and addresses
    let provider = ProviderBuilder::new()
        .connect("https://sepolia-rollup.arbitrum.io/rpc")
        .await?;

    let contract_address = Address::from_str("0x2a9b9c988393f46a2537b0ff11e98c2c15a95afe")?;
    let trading_contract = TradingContract::new(contract_address, provider);
    let trader_address = Address::from_str("0x742d35Cc6634C0532925a3b8D53A2e1C5BC2D8db")?;

    println!("✅ Step 2: Initialized contract and addresses");

    // Step 3: Configure transaction parameters
    let tx_params_with_gas = UnsignedTransactionParams {
        from: trader_address,
        include_gas_estimates: true,
        include_nonce: true,
    };

    let tx_params_minimal = UnsignedTransactionParams {
        from: trader_address,
        include_gas_estimates: false,
        include_nonce: false,
    };

    println!("✅ Step 3: Configured transaction parameters");

    // Step 4: Test all trading operations
    println!("\n🚀 Step 4: Testing all trading operations...");

    let mut results = Vec::new();

    // 4.1 Open Trade
    println!("   📈 Testing open trade...");
    let trade = Trade {
        collateral: U256::from(1000_000000u64),
        open_price: 0,
        tp: 45000_000000000000000000u128,
        sl: 35000_000000000000000000u128,
        trader: trader_address,
        leverage: 1000,
        pair_index: 0,
        index: 0,
        buy: true,
    };

    match trading_contract
        .open_trade_unsigned(
            trade,
            OpenOrderType::Market,
            U256::from(100),
            tx_params_with_gas.clone(),
        )
        .await
    {
        Ok(tx) => {
            validate_transaction(&tx, "Open Trade")?;
            results.push(("Open Trade", true, tx.data.len()));
        }
        Err(e) => {
            println!("      ❌ Open trade failed: {}", e);
            results.push(("Open Trade", false, 0));
        }
    }

    // 4.2 Close Trade
    println!("   📉 Testing close trade...");
    match trading_contract
        .close_trade_market_unsigned(0, 0, 10000, tx_params_with_gas.clone())
        .await
    {
        Ok(tx) => {
            validate_transaction(&tx, "Close Trade")?;
            results.push(("Close Trade", true, tx.data.len()));
        }
        Err(e) => {
            println!("      ❌ Close trade failed: {}", e);
            results.push(("Close Trade", false, 0));
        }
    }

    // 4.3 Update Take Profit
    println!("   📊 Testing update take profit...");
    match trading_contract
        .update_tp_unsigned(
            0,
            0,
            U192::from(50000_000000000000000000u128),
            tx_params_with_gas.clone(),
        )
        .await
    {
        Ok(tx) => {
            validate_transaction(&tx, "Update TP")?;
            results.push(("Update TP", true, tx.data.len()));
        }
        Err(e) => {
            println!("      ❌ Update TP failed: {}", e);
            results.push(("Update TP", false, 0));
        }
    }

    // 4.4 Update Stop Loss
    println!("   🛑 Testing update stop loss...");
    match trading_contract
        .update_sl_unsigned(
            0,
            0,
            U192::from(30000_000000000000000000u128),
            tx_params_with_gas.clone(),
        )
        .await
    {
        Ok(tx) => {
            validate_transaction(&tx, "Update SL")?;
            results.push(("Update SL", true, tx.data.len()));
        }
        Err(e) => {
            println!("      ❌ Update SL failed: {}", e);
            results.push(("Update SL", false, 0));
        }
    }

    // 4.5 Cancel Order
    println!("   ❌ Testing cancel order...");
    match trading_contract
        .cancel_open_limit_order_unsigned(0, 0, tx_params_with_gas.clone())
        .await
    {
        Ok(tx) => {
            validate_transaction(&tx, "Cancel Order")?;
            results.push(("Cancel Order", true, tx.data.len()));
        }
        Err(e) => {
            println!("      ❌ Cancel order failed: {}", e);
            results.push(("Cancel Order", false, 0));
        }
    }

    // Step 5: Test parameter variations
    println!("\n⚙️  Step 5: Testing parameter variations...");

    // Test with minimal params
    println!("   🔧 Testing minimal parameters...");
    match trading_contract
        .update_tp_unsigned(
            0,
            0,
            U192::from(55000_000000000000000000u128),
            tx_params_minimal.clone(),
        )
        .await
    {
        Ok(tx) => {
            if tx.gas_limit.is_some() || tx.gas_price.is_some() || tx.nonce.is_some() {
                println!("      ❌ Expected minimal params but got gas estimates or nonce");
            } else {
                println!("      ✅ Minimal parameters working correctly");
                results.push(("Minimal Params", true, tx.data.len()));
            }
        }
        Err(e) => {
            println!("      ❌ Minimal params test failed: {}", e);
            results.push(("Minimal Params", false, 0));
        }
    }

    // Step 6: Test JSON serialization workflow
    println!("\n📄 Step 6: Testing complete JSON workflow...");

    let test_trade = Trade {
        collateral: U256::from(500_000000u64),
        open_price: 0,
        tp: 42000_000000000000000000u128,
        sl: 38000_000000000000000000u128,
        trader: trader_address,
        leverage: 500, // 5x leverage
        pair_index: 1, // ETH/USD
        index: 0,
        buy: false, // Short position
    };

    match trading_contract
        .open_trade_unsigned(
            test_trade,
            OpenOrderType::Market,
            U256::from(50),
            tx_params_with_gas.clone(),
        )
        .await
    {
        Ok(unsigned_tx) => {
            // Serialize to JSON
            let json = serde_json::to_string_pretty(&unsigned_tx)?;
            println!("   ✅ JSON serialization successful");

            // Deserialize from JSON
            let deserialized: ostium_rust_sdk::UnsignedTransaction = serde_json::from_str(&json)?;
            println!("   ✅ JSON deserialization successful");

            // Validate round-trip
            if unsigned_tx.to == deserialized.to
                && unsigned_tx.data == deserialized.data
                && unsigned_tx.value == deserialized.value
                && unsigned_tx.chain_id == deserialized.chain_id
            {
                println!("   ✅ JSON round-trip validation successful");
                results.push(("JSON Workflow", true, json.len()));
            } else {
                println!("   ❌ JSON round-trip validation failed");
                results.push(("JSON Workflow", false, 0));
            }
        }
        Err(e) => {
            println!("   ❌ JSON workflow test failed: {}", e);
            results.push(("JSON Workflow", false, 0));
        }
    }

    // Step 7: Test batch operations
    println!("\n🔄 Step 7: Testing batch operations...");

    let mut batch_txs = Vec::new();

    // Create multiple transactions for batch execution
    if let Ok(tx1) = trading_contract
        .update_tp_unsigned(
            0,
            0,
            U192::from(48000_000000000000000000u128),
            tx_params_with_gas.clone(),
        )
        .await
    {
        batch_txs.push(("Batch TP", tx1));
    }

    if let Ok(tx2) = trading_contract
        .update_sl_unsigned(
            0,
            0,
            U192::from(32000_000000000000000000u128),
            tx_params_with_gas.clone(),
        )
        .await
    {
        batch_txs.push(("Batch SL", tx2));
    }

    if !batch_txs.is_empty() {
        let batch_json =
            serde_json::to_string_pretty(&batch_txs.iter().map(|(_, tx)| tx).collect::<Vec<_>>())?;
        println!(
            "   ✅ Batch operations successful ({} transactions)",
            batch_txs.len()
        );
        results.push(("Batch Operations", true, batch_json.len()));
    } else {
        println!("   ❌ Batch operations failed");
        results.push(("Batch Operations", false, 0));
    }

    // Step 8: Display comprehensive results
    println!("\n📊 Step 8: Comprehensive Results");
    println!("=================================");

    let successful = results.iter().filter(|(_, success, _)| *success).count();
    let total = results.len();

    println!(
        "Overall Success Rate: {}/{} ({:.1}%)",
        successful,
        total,
        (successful as f64 / total as f64) * 100.0
    );
    println!();

    for (operation, success, size) in &results {
        let status = if *success { "" } else { "" };
        let size_info = if *size > 0 {
            format!(" ({} bytes)", size)
        } else {
            String::new()
        };
        println!("{} {}{}", status, operation, size_info);
    }

    // Step 9: Final validation
    println!("\n🎯 Step 9: Final Validation");
    println!("============================");

    if successful == total {
        println!("🎉 ALL TESTS PASSED!");
        println!("   ✅ Unsigned transaction generation is working correctly");
        println!("   ✅ All trading operations are supported");
        println!("   ✅ JSON serialization/deserialization works");
        println!("   ✅ Parameter variations are handled correctly");
        println!("   ✅ Batch operations are supported");
        println!();
        println!("🚀 The implementation is ready for production use!");
    } else {
        println!("⚠️  Some tests failed. Review the implementation.");
        return Err("Test failures detected".into());
    }

    println!("\n💡 Usage Summary:");
    println!("   1. Create OstiumClient without signer for read-only mode");
    println!("   2. Use TradingContract directly for unsigned transaction generation");
    println!("   3. Configure UnsignedTransactionParams with desired options");
    println!("   4. Call *_unsigned methods to generate transaction data");
    println!("   5. Serialize to JSON and send to frontend for signing");

    Ok(())
}

/// Validates that an unsigned transaction has the expected structure
fn validate_transaction(
    tx: &ostium_rust_sdk::UnsignedTransaction,
    operation: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    // Check basic structure
    if tx.data.is_empty() {
        return Err(format!("Transaction data is empty for {}", operation).into());
    }

    if tx.data.len() < 4 {
        return Err(format!(
            "Transaction data too short for {} ({})",
            operation,
            tx.data.len()
        )
        .into());
    }

    if tx.chain_id == 0 {
        return Err(format!("Invalid chain ID for {}", operation).into());
    }

    if tx.to == Address::ZERO {
        return Err(format!("Invalid contract address for {}", operation).into());
    }

    // Check function selector (first 4 bytes should not be all zeros)
    let selector = &tx.data[0..4];
    if selector.iter().all(|&b| b == 0) {
        return Err(format!("Invalid function selector for {}", operation).into());
    }

    println!(
        "{} validation passed ({} bytes)",
        operation,
        tx.data.len()
    );
    Ok(())
}