ostium-rust-sdk 0.1.0

Rust SDK for interacting with the Ostium trading platform on Arbitrum
Documentation
//! Working example of unsigned transaction generation
//!
//! This example demonstrates how to build unsigned transactions using direct
//! contract calls, bypassing the higher-level client methods that may fail
//! due to contract state requirements.

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>> {
    // Initialize logging
    tracing_subscriber::fmt::init();

    println!("🚀 Working Unsigned Transaction Example");
    println!("======================================");

    // Create client without a signer (read-only mode)
    let _client = OstiumClient::new(Network::Testnet).await?;
    println!("✅ Connected to Ostium testnet (no signer required)");

    // Simulate a trader address (this would come from your frontend)
    let trader_address = Address::from_str("0x742d35Cc6634C0532925a3b8D53A2e1c5Bc2D8db")?;
    println!("👤 Using trader address: {}", trader_address);

    // ==================== DIRECT CONTRACT INTERACTION ====================

    println!("\n🔧 Building unsigned transactions using direct contract calls...");

    // Create a provider for direct contract interaction
    let provider = ProviderBuilder::new()
        .connect("https://sepolia-rollup.arbitrum.io/rpc")
        .await?;

    // Create trading contract instance with real testnet address
    let contract_address = Address::from_str("0x2a9b9c988393f46a2537b0ff11e98c2c15a95afe")?;
    let trading_contract = TradingContract::new(contract_address, provider);

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

    // ==================== EXAMPLE 1: OPEN TRADE ====================

    println!("\n📈 Example 1: Building unsigned transaction for opening a trade");

    // Create test trade data (using known valid values)
    let trade = Trade {
        collateral: U256::from(1000_000000u64), // 1000 USDC (6 decimals)
        open_price: 0,                          // Will be set by contract for market orders
        tp: 45000_000000000000000000u128,       // 45000 USD (18 decimals)
        sl: 35000_000000000000000000u128,       // 35000 USD (18 decimals)
        trader: trader_address,
        leverage: 1000, // 10x leverage (100 = 1x in basis points)
        pair_index: 0,  // Assuming BTC/USD is index 0
        index: 0,       // Trade index (will be set by contract)
        buy: true,      // Long position
    };

    match trading_contract
        .open_trade_unsigned(
            trade.clone(),
            OpenOrderType::Market,
            U256::from(100), // 1% slippage (100 basis points)
            tx_params.clone(),
        )
        .await
    {
        Ok(unsigned_tx) => {
            println!("✅ Successfully built open trade unsigned transaction!");
            print_transaction_details(&unsigned_tx, "Open Trade");

            // Show how this would be sent to frontend
            let frontend_payload = serde_json::to_string_pretty(&unsigned_tx)?;
            println!("\n📤 JSON payload for frontend (open trade):");
            println!("{}", frontend_payload);
        }
        Err(e) => {
            println!("❌ Failed to build open trade transaction: {}", e);
        }
    }

    // ==================== EXAMPLE 2: CLOSE TRADE ====================

    println!("\n📉 Example 2: Building unsigned transaction for closing a trade");

    match trading_contract
        .close_trade_market_unsigned(
            0,     // pair_index
            0,     // index
            10000, // 100% close (10000 basis points)
            tx_params.clone(),
        )
        .await
    {
        Ok(unsigned_tx) => {
            println!("✅ Successfully built close trade unsigned transaction!");
            print_transaction_details(&unsigned_tx, "Close Trade");
        }
        Err(e) => {
            println!("❌ Failed to build close trade transaction: {}", e);
        }
    }

    // ==================== EXAMPLE 3: UPDATE TAKE PROFIT ====================

    println!("\n📊 Example 3: Building unsigned transaction for updating take profit");

    match trading_contract
        .update_tp_unsigned(
            0,                                        // pair_index
            0,                                        // index
            U192::from(50000_000000000000000000u128), // 50000 USD (18 decimals)
            tx_params.clone(),
        )
        .await
    {
        Ok(unsigned_tx) => {
            println!("✅ Successfully built update TP unsigned transaction!");
            print_transaction_details(&unsigned_tx, "Update Take Profit");
        }
        Err(e) => {
            println!("❌ Failed to build update TP transaction: {}", e);
        }
    }

    // ==================== EXAMPLE 4: UPDATE STOP LOSS ====================

    println!("\n🛑 Example 4: Building unsigned transaction for updating stop loss");

    match trading_contract
        .update_sl_unsigned(
            0,                                        // pair_index
            0,                                        // index
            U192::from(30000_000000000000000000u128), // 30000 USD (18 decimals)
            tx_params.clone(),
        )
        .await
    {
        Ok(unsigned_tx) => {
            println!("✅ Successfully built update SL unsigned transaction!");
            print_transaction_details(&unsigned_tx, "Update Stop Loss");
        }
        Err(e) => {
            println!("❌ Failed to build update SL transaction: {}", e);
        }
    }

    // ==================== EXAMPLE 5: CANCEL ORDER ====================

    println!("\n❌ Example 5: Building unsigned transaction for canceling an order");

    match trading_contract
        .cancel_open_limit_order_unsigned(
            0, // pair_index
            0, // index
            tx_params.clone(),
        )
        .await
    {
        Ok(unsigned_tx) => {
            println!("✅ Successfully built cancel order unsigned transaction!");
            print_transaction_details(&unsigned_tx, "Cancel Order");
        }
        Err(e) => {
            println!("❌ Failed to build cancel order transaction: {}", e);
        }
    }

    // ==================== EXAMPLE 6: BATCH TRANSACTIONS ====================

    println!("\n🔄 Example 6: Creating multiple unsigned transactions (batch)");

    let mut batch_transactions = Vec::new();

    // Build multiple transactions for batch execution
    if let Ok(update_tp_tx) = trading_contract
        .update_tp_unsigned(
            0,
            0,
            U192::from(55000_000000000000000000u128),
            tx_params.clone(),
        )
        .await
    {
        batch_transactions.push(("Update TP", update_tp_tx));
    }

    if let Ok(update_sl_tx) = trading_contract
        .update_sl_unsigned(
            0,
            0,
            U192::from(25000_000000000000000000u128),
            tx_params.clone(),
        )
        .await
    {
        batch_transactions.push(("Update SL", update_sl_tx));
    }

    if !batch_transactions.is_empty() {
        println!(
            "✅ Successfully created {} batch transactions!",
            batch_transactions.len()
        );

        for (i, (name, tx)) in batch_transactions.iter().enumerate() {
            println!("   {}. {}: {} bytes", i + 1, name, tx.data.len());
        }

        // Show batch JSON
        let transactions_only: Vec<_> = batch_transactions.iter().map(|(_, tx)| tx).collect();
        let batch_json = serde_json::to_string_pretty(&transactions_only)?;
        println!("\n📤 Batch transactions JSON:");
        println!("{}", batch_json);
    } else {
        println!("❌ No batch transactions were successfully created");
    }

    // ==================== EXAMPLE 7: TRANSACTION ANALYSIS ====================

    println!("\n🔍 Example 7: Transaction data analysis");

    // Create a simple transaction for analysis
    if let Ok(simple_tx) = trading_contract
        .close_trade_market_unsigned(0, 0, 5000, tx_params.clone())
        .await
    {
        analyze_transaction_data(&simple_tx);
    }

    // ==================== SUMMARY ====================

    println!("\n✨ Summary");
    println!("=========");
    println!("• Unsigned transactions can be built without requiring private keys");
    println!("• Gas estimates and nonces are automatically included when requested");
    println!("• Transaction data is properly ABI-encoded for smart contract interaction");
    println!("• Batch operations allow multiple transactions to be prepared together");
    println!("• All transaction data is serializable to JSON for frontend integration");
    println!();
    println!("🔐 Security note: Private keys never leave the user's wallet/frontend");
    println!("⚡ Performance: Backend pre-builds transactions, frontend just signs");

    Ok(())
}

/// Helper function to print transaction details in a formatted way
fn print_transaction_details(tx: &ostium_rust_sdk::UnsignedTransaction, operation: &str) {
    println!("📋 {} Transaction Details:", operation);
    println!("   To: {}", tx.to);
    println!("   Data length: {} bytes", tx.data.len());
    println!("   Value: {} ETH", tx.value);
    println!("   Chain ID: {}", tx.chain_id);

    if let Some(gas_limit) = tx.gas_limit {
        println!("   Gas limit: {}", gas_limit);
    }
    if let Some(gas_price) = tx.gas_price {
        println!(
            "   Gas price: {} gwei",
            gas_price / U256::from(1_000_000_000u64)
        );
    }
    if let Some(nonce) = tx.nonce {
        println!("   Nonce: {}", nonce);
    }
}

/// Helper function to analyze transaction data
fn analyze_transaction_data(tx: &ostium_rust_sdk::UnsignedTransaction) {
    println!("🔍 Transaction Data Analysis:");
    println!("   Contract: {}", tx.to);
    println!("   Data size: {} bytes", tx.data.len());

    if tx.data.len() >= 4 {
        let function_selector = &tx.data[0..4];
        println!("   Function selector: 0x{}", hex::encode(function_selector));

        // Try to identify the function based on common selectors
        let _selector_hex = hex::encode(function_selector);
        let function_name = "Unknown function";
        println!("   Likely function: {}", function_name);
    }

    if tx.data.len() > 4 {
        let parameter_data = &tx.data[4..];
        println!("   Parameter data: {} bytes", parameter_data.len());
    }

    // Show first few bytes of data for debugging
    let preview_len = std::cmp::min(32, tx.data.len());
    let preview = &tx.data[0..preview_len];
    println!("   Data preview: 0x{}", hex::encode(preview));
}