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!("==========================================");
let client = OstiumClient::new(Network::Testnet).await?;
println!("✅ Step 1: Created read-only client");
assert!(!client.has_signer(), "Client should not have a signer");
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");
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");
println!("\n🚀 Step 4: Testing all trading operations...");
let mut results = Vec::new();
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));
}
}
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));
}
}
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));
}
}
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));
}
}
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));
}
}
println!("\n⚙️ Step 5: Testing parameter variations...");
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));
}
}
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, pair_index: 1, index: 0,
buy: false, };
match trading_contract
.open_trade_unsigned(
test_trade,
OpenOrderType::Market,
U256::from(50),
tx_params_with_gas.clone(),
)
.await
{
Ok(unsigned_tx) => {
let json = serde_json::to_string_pretty(&unsigned_tx)?;
println!(" ✅ JSON serialization successful");
let deserialized: ostium_rust_sdk::UnsignedTransaction = serde_json::from_str(&json)?;
println!(" ✅ JSON deserialization successful");
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));
}
}
println!("\n🔄 Step 7: Testing batch operations...");
let mut batch_txs = Vec::new();
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));
}
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);
}
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(())
}
fn validate_transaction(
tx: &ostium_rust_sdk::UnsignedTransaction,
operation: &str,
) -> Result<(), Box<dyn std::error::Error>> {
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());
}
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(())
}