rest.rs
1 // Copyright (c) 2025 ADnet Contributors 2 // SPDX-License-Identifier: Apache-2.0 3 4 //! REST API for the DELTA node 5 //! 6 //! Provides HTTP endpoints for: 7 //! - Node status and health 8 //! - Block and transaction queries 9 //! - Trading operations (exchange integration) 10 //! - Bridge operations 11 12 use axum::{ 13 Router, 14 extract::{Path, State}, 15 http::StatusCode, 16 response::Json, 17 routing::{delete, get, post}, 18 }; 19 use serde::{Deserialize, Serialize}; 20 use std::sync::Arc; 21 use tokio::sync::RwLock; 22 23 use crate::{NodeConfig, NodeState}; 24 use deltavm_execution::{ 25 BridgeTx, BridgeTxResult, DeltaRuntime, OrderSide, OrderType, TradingPair, TradingResult, 26 TradingTx, 27 }; 28 29 /// Shared state for the REST API (uses separate copies - legacy) 30 pub struct RestState { 31 pub config: NodeConfig, 32 pub node_state: RwLock<NodeState>, 33 pub runtime: RwLock<DeltaRuntime>, 34 } 35 36 /// Shared state for the REST API that shares references with the node 37 /// This ensures exchange state (orders, trades) is properly shared 38 pub struct RestStateShared { 39 pub config: NodeConfig, 40 pub node_state: Arc<RwLock<NodeState>>, 41 pub runtime: Arc<RwLock<DeltaRuntime>>, 42 } 43 44 /// Node status response 45 #[derive(Serialize)] 46 pub struct StatusResponse { 47 pub version: String, 48 pub network: String, 49 pub node_type: String, 50 pub block_height: u32, 51 pub is_running: bool, 52 pub is_synced: bool, 53 pub peer_count: u32, 54 } 55 56 /// Health check response 57 #[derive(Serialize)] 58 pub struct HealthResponse { 59 pub status: String, 60 pub uptime_seconds: u64, 61 } 62 63 /// Block response 64 #[derive(Serialize)] 65 pub struct BlockResponse { 66 pub height: u32, 67 pub hash: String, 68 pub timestamp: u64, 69 pub tx_count: u32, 70 } 71 72 /// Bridge status response 73 #[derive(Serialize)] 74 pub struct BridgeStatusResponse { 75 pub total_deposits: u64, 76 pub total_withdrawals: u64, 77 pub pending_withdrawals: u64, 78 pub total_fees: u64, 79 pub is_halted: bool, 80 } 81 82 /// Deposit request 83 #[derive(Deserialize)] 84 pub struct DepositRequest { 85 pub attestation: String, // hex encoded 86 pub amount: u64, 87 pub recipient: String, 88 } 89 90 /// Withdrawal request 91 #[derive(Deserialize)] 92 pub struct WithdrawalRequest { 93 pub amount: u64, 94 pub alpha_recipient: String, 95 } 96 97 /// Transaction result response 98 #[derive(Serialize)] 99 pub struct TxResultResponse { 100 pub success: bool, 101 pub message: String, 102 #[serde(skip_serializing_if = "Option::is_none")] 103 pub amount: Option<u64>, 104 #[serde(skip_serializing_if = "Option::is_none")] 105 pub fee: Option<u64>, 106 #[serde(skip_serializing_if = "Option::is_none")] 107 pub nonce: Option<u64>, 108 } 109 110 // ============================================================================ 111 // Exchange Types (F-D01 to F-D07) 112 // ============================================================================ 113 114 /// Limit order request 115 #[derive(Deserialize)] 116 pub struct LimitOrderRequest { 117 /// User address (sender) 118 pub sender: String, 119 /// Trading pair (e.g., "DELTA/ALPHA") 120 pub pair: String, 121 /// Order side: "buy" or "sell" 122 pub side: String, 123 /// Order quantity 124 pub quantity: u64, 125 /// Limit price 126 pub price: u64, 127 /// Time-in-force: "gtc" (default), "ioc", "fok", "day" 128 #[serde(default)] 129 pub time_in_force: Option<String>, 130 } 131 132 /// Market order request 133 #[derive(Deserialize)] 134 pub struct MarketOrderRequest { 135 /// User address (sender) 136 pub sender: String, 137 /// Trading pair (e.g., "DELTA/ALPHA") 138 pub pair: String, 139 /// Order side: "buy" or "sell" 140 pub side: String, 141 /// Order quantity 142 pub quantity: u64, 143 } 144 145 /// Cancel order request 146 #[derive(Deserialize)] 147 pub struct CancelOrderRequest { 148 /// User address (sender) 149 pub sender: String, 150 } 151 152 /// Order response 153 #[derive(Serialize)] 154 pub struct OrderResponse { 155 pub success: bool, 156 pub message: String, 157 #[serde(skip_serializing_if = "Option::is_none")] 158 pub order_id: Option<String>, 159 #[serde(skip_serializing_if = "Option::is_none")] 160 pub trades_executed: Option<usize>, 161 #[serde(skip_serializing_if = "Option::is_none")] 162 pub filled_quantity: Option<u64>, 163 #[serde(skip_serializing_if = "Option::is_none")] 164 pub status: Option<String>, 165 } 166 167 /// Order book level for API response 168 #[derive(Serialize)] 169 pub struct OrderBookLevelResponse { 170 pub price: u64, 171 pub quantity: u64, 172 pub order_count: u32, 173 } 174 175 /// Order book response 176 #[derive(Serialize)] 177 pub struct OrderBookResponse { 178 pub pair: String, 179 pub bids: Vec<OrderBookLevelResponse>, 180 pub asks: Vec<OrderBookLevelResponse>, 181 #[serde(skip_serializing_if = "Option::is_none")] 182 pub best_bid: Option<u64>, 183 #[serde(skip_serializing_if = "Option::is_none")] 184 pub best_ask: Option<u64>, 185 #[serde(skip_serializing_if = "Option::is_none")] 186 pub spread: Option<u64>, 187 } 188 189 /// Trade response for API 190 #[derive(Serialize)] 191 pub struct TradeResponse { 192 pub id: String, 193 pub pair: String, 194 pub price: u64, 195 pub quantity: u64, 196 pub maker_fee: u64, 197 pub taker_fee: u64, 198 pub timestamp: u32, 199 } 200 201 /// User order for API response 202 #[derive(Serialize)] 203 pub struct UserOrderResponse { 204 pub id: String, 205 pub pair: String, 206 pub side: String, 207 pub order_type: String, 208 pub price: u64, 209 pub quantity: u64, 210 pub filled_quantity: u64, 211 pub status: String, 212 pub created_at: u32, 213 } 214 215 /// Balance response 216 #[derive(Serialize)] 217 pub struct BalanceResponse { 218 pub address: String, 219 pub balances: Vec<AssetBalance>, 220 } 221 222 /// Asset balance entry 223 #[derive(Serialize)] 224 pub struct AssetBalance { 225 pub asset: String, 226 pub available: u64, 227 pub locked: u64, 228 } 229 230 /// Build the REST API router (legacy - uses separate state copies) 231 pub fn build_router(state: Arc<RestState>) -> Router { 232 Router::new() 233 // Status endpoints 234 .route("/", get(root)) 235 .route("/health", get(health)) 236 .route("/status", get(status)) 237 // Block endpoints 238 .route("/block/latest", get(get_latest_block)) 239 .route("/block/height", get(get_block_height)) 240 // Bridge endpoints (F-X10, F-X11, F-X12) 241 .route("/bridge/status", get(bridge_status)) 242 .route("/bridge/deposit", post(process_deposit)) 243 .route("/bridge/withdraw", post(request_withdrawal)) 244 // Exchange Order Endpoints (F-D01 to F-D07) 245 .route("/orders/limit", post(place_limit_order)) 246 .route("/orders/market", post(place_market_order)) 247 .route("/orders/{id}", delete(cancel_order)) 248 .route("/orders", get(list_user_orders)) 249 // Order Book Endpoints (F-D06) 250 .route("/orderbook/{pair}", get(get_order_book)) 251 .route("/trades/{pair}", get(get_recent_trades)) 252 // Balance Endpoints 253 .route("/balance/{address}", get(get_balance)) 254 // Legacy trading endpoints (backward compatibility) 255 .route("/trading/pairs", get(get_trading_pairs)) 256 .route("/trading/orderbook/{pair}", get(get_orderbook)) 257 .with_state(state) 258 } 259 260 /// Build the REST API router with shared state (preferred for exchange operations) 261 /// This router shares the runtime with the node, ensuring all exchange state is synchronized 262 pub fn build_router_shared(state: Arc<RestStateShared>) -> Router { 263 Router::new() 264 // Status endpoints 265 .route("/", get(root)) 266 .route("/health", get(health)) 267 .route("/status", get(status_shared)) 268 // Block endpoints 269 .route("/block/latest", get(get_latest_block_shared)) 270 .route("/block/height", get(get_block_height_shared)) 271 // Bridge endpoints (F-X10, F-X11, F-X12) 272 .route("/bridge/status", get(bridge_status_shared)) 273 .route("/bridge/deposit", post(process_deposit_shared)) 274 .route("/bridge/withdraw", post(request_withdrawal_shared)) 275 // Exchange Order Endpoints (F-D01 to F-D07) 276 .route("/orders/limit", post(place_limit_order_shared)) 277 .route("/orders/market", post(place_market_order_shared)) 278 .route("/orders/{id}", delete(cancel_order_shared)) 279 .route("/orders", get(list_user_orders_shared)) 280 // Order Book Endpoints (F-D06) 281 .route("/orderbook/{pair}", get(get_order_book_shared)) 282 .route("/trades/{pair}", get(get_recent_trades_shared)) 283 // Balance Endpoints 284 .route("/balance/{address}", get(get_balance_shared)) 285 // Legacy trading endpoints (backward compatibility) 286 .route("/trading/pairs", get(get_trading_pairs)) 287 .route("/trading/orderbook/{pair}", get(get_orderbook)) 288 .with_state(state) 289 } 290 291 /// Root endpoint 292 async fn root() -> &'static str { 293 "DeltaOS v0.1.0 - DELTA Chain Node" 294 } 295 296 /// Health check 297 async fn health() -> Json<HealthResponse> { 298 Json(HealthResponse { 299 status: "ok".to_string(), 300 uptime_seconds: 0, // TODO: Track actual uptime 301 }) 302 } 303 304 /// Node status 305 async fn status(State(state): State<Arc<RestState>>) -> Json<StatusResponse> { 306 let node_state = state.node_state.read().await; 307 308 Json(StatusResponse { 309 version: "0.1.0".to_string(), 310 network: "DeltaV0".to_string(), 311 node_type: format!("{:?}", state.config.node_type), 312 block_height: node_state.block_height, 313 is_running: node_state.is_running, 314 is_synced: node_state.is_synced(), 315 peer_count: node_state.peer_count, 316 }) 317 } 318 319 /// Get latest block 320 async fn get_latest_block(State(state): State<Arc<RestState>>) -> Json<BlockResponse> { 321 let node_state = state.node_state.read().await; 322 let runtime = state.runtime.read().await; 323 324 Json(BlockResponse { 325 height: node_state.block_height, 326 hash: format!("delta_{:08x}", runtime.block_height), 327 timestamp: std::time::SystemTime::now() 328 .duration_since(std::time::UNIX_EPOCH) 329 .unwrap_or_default() 330 .as_secs(), 331 tx_count: 0, 332 }) 333 } 334 335 /// Get current block height 336 async fn get_block_height(State(state): State<Arc<RestState>>) -> String { 337 let node_state = state.node_state.read().await; 338 node_state.block_height.to_string() 339 } 340 341 /// Bridge status (F-X10, F-X11, F-X12) 342 async fn bridge_status(State(state): State<Arc<RestState>>) -> Json<BridgeStatusResponse> { 343 let runtime = state.runtime.read().await; 344 let stats = (*runtime).bridge_stats(); 345 346 Json(BridgeStatusResponse { 347 total_deposits: stats.total_deposits, 348 total_withdrawals: stats.total_withdrawals, 349 pending_withdrawals: stats.pending_withdrawals, 350 total_fees: stats.total_fees, 351 is_halted: stats.is_halted, 352 }) 353 } 354 355 /// Process deposit (F-X10) 356 async fn process_deposit( 357 State(state): State<Arc<RestState>>, 358 Json(req): Json<DepositRequest>, 359 ) -> Result<Json<TxResultResponse>, StatusCode> { 360 use deltavm_console::{Address, DeltaV0}; 361 use std::str::FromStr; 362 363 // Parse recipient address 364 let recipient: Address<DeltaV0> = 365 Address::from_str(&req.recipient).map_err(|_| StatusCode::BAD_REQUEST)?; 366 367 // Decode attestation 368 let attestation = hex::decode(&req.attestation).unwrap_or_default(); 369 370 // Create bridge transaction 371 let tx = BridgeTx::LockAlpha { 372 attestation, 373 amount: req.amount, 374 recipient, 375 }; 376 377 // Execute 378 let mut runtime = state.runtime.write().await; 379 let result = (*runtime).execute_bridge(tx); 380 381 match result { 382 BridgeTxResult::DepositSuccess { amount, fee } => Ok(Json(TxResultResponse { 383 success: true, 384 message: "Deposit processed successfully".to_string(), 385 amount: Some(amount), 386 fee: Some(fee), 387 nonce: None, 388 })), 389 BridgeTxResult::Failed { reason } => Ok(Json(TxResultResponse { 390 success: false, 391 message: reason, 392 amount: None, 393 fee: None, 394 nonce: None, 395 })), 396 _ => Ok(Json(TxResultResponse { 397 success: false, 398 message: "Unexpected result".to_string(), 399 amount: None, 400 fee: None, 401 nonce: None, 402 })), 403 } 404 } 405 406 /// Request withdrawal (F-X11) 407 async fn request_withdrawal( 408 State(state): State<Arc<RestState>>, 409 Json(req): Json<WithdrawalRequest>, 410 ) -> Result<Json<TxResultResponse>, StatusCode> { 411 // Create bridge transaction 412 let tx = BridgeTx::UnlockAlpha { 413 amount: req.amount, 414 alpha_recipient: req.alpha_recipient.into_bytes(), 415 }; 416 417 // Execute 418 let mut runtime = state.runtime.write().await; 419 let result = (*runtime).execute_bridge(tx); 420 421 match result { 422 BridgeTxResult::WithdrawalAccepted { nonce, amount, fee } => Ok(Json(TxResultResponse { 423 success: true, 424 message: "Withdrawal request accepted".to_string(), 425 amount: Some(amount), 426 fee: Some(fee), 427 nonce: Some(nonce), 428 })), 429 BridgeTxResult::Failed { reason } => Ok(Json(TxResultResponse { 430 success: false, 431 message: reason, 432 amount: None, 433 fee: None, 434 nonce: None, 435 })), 436 _ => Ok(Json(TxResultResponse { 437 success: false, 438 message: "Unexpected result".to_string(), 439 amount: None, 440 fee: None, 441 nonce: None, 442 })), 443 } 444 } 445 446 /// Get trading pairs (placeholder) 447 async fn get_trading_pairs() -> Json<Vec<&'static str>> { 448 Json(vec!["DELTA/ALPHA", "DELTA/USD", "ALPHA/USD"]) 449 } 450 451 /// Get orderbook (placeholder - legacy endpoint) 452 async fn get_orderbook() -> Json<serde_json::Value> { 453 Json(serde_json::json!({ 454 "bids": [], 455 "asks": [], 456 "last_price": 0, 457 "volume_24h": 0 458 })) 459 } 460 461 // ============================================================================ 462 // Exchange Endpoint Handlers (F-D01 to F-D07) 463 // ============================================================================ 464 465 /// Parse trading pair from string (e.g., "DELTA/ALPHA" or "DELTA_ALPHA") 466 fn parse_trading_pair(pair_str: &str) -> Result<TradingPair, String> { 467 let normalized = pair_str.to_uppercase().replace('_', "/"); 468 match normalized.as_str() { 469 "DELTA/ALPHA" => Ok(TradingPair::DeltaAlpha), 470 _ => { 471 // Try to parse as custom pair 472 let parts: Vec<&str> = normalized.split('/').collect(); 473 if parts.len() == 2 { 474 Ok(TradingPair::Custom { 475 base: parts[0].to_string(), 476 quote: parts[1].to_string(), 477 }) 478 } else { 479 Err(format!("Invalid trading pair: {}", pair_str)) 480 } 481 } 482 } 483 } 484 485 /// Parse order side from string 486 fn parse_order_side(side_str: &str) -> Result<OrderSide, String> { 487 match side_str.to_lowercase().as_str() { 488 "buy" => Ok(OrderSide::Buy), 489 "sell" => Ok(OrderSide::Sell), 490 _ => Err(format!( 491 "Invalid order side: {}. Must be 'buy' or 'sell'", 492 side_str 493 )), 494 } 495 } 496 497 /// Format trading pair to string 498 fn format_trading_pair(pair: &TradingPair) -> String { 499 match pair { 500 TradingPair::DeltaAlpha => "DELTA/ALPHA".to_string(), 501 TradingPair::Custom { base, quote } => format!("{}/{}", base, quote), 502 } 503 } 504 505 /// POST /orders/limit - Place a limit order (F-D01) 506 async fn place_limit_order( 507 State(state): State<Arc<RestState>>, 508 Json(req): Json<LimitOrderRequest>, 509 ) -> Result<Json<OrderResponse>, StatusCode> { 510 use deltavm_console::{Address, DeltaV0}; 511 use std::str::FromStr; 512 513 // Parse sender address 514 let sender: Address<DeltaV0> = Address::from_str(&req.sender).map_err(|_| { 515 tracing::warn!("Invalid sender address: {}", req.sender); 516 StatusCode::BAD_REQUEST 517 })?; 518 519 // Parse trading pair 520 let pair = parse_trading_pair(&req.pair).map_err(|e| { 521 tracing::warn!("Invalid trading pair: {}", e); 522 StatusCode::BAD_REQUEST 523 })?; 524 525 // Parse order side 526 let side = parse_order_side(&req.side).map_err(|e| { 527 tracing::warn!("Invalid order side: {}", e); 528 StatusCode::BAD_REQUEST 529 })?; 530 531 // Create trading transaction 532 let tx = TradingTx::PlaceOrder { 533 pair, 534 side, 535 quantity: req.quantity, 536 price: Some(req.price), 537 order_type: OrderType::Limit, 538 }; 539 540 // Execute trading transaction 541 let mut runtime = state.runtime.write().await; 542 let result = runtime.execute_trading(sender, tx); 543 544 match result { 545 Ok(TradingResult::OrderPlaced { 546 match_result, 547 trades_executed, 548 }) => { 549 let order_id = hex::encode(&match_result.order.id.0[..16]); 550 let status = format!("{:?}", match_result.order.status); 551 552 Ok(Json(OrderResponse { 553 success: true, 554 message: format!("Limit order placed. {} trades executed.", trades_executed), 555 order_id: Some(order_id), 556 trades_executed: Some(trades_executed), 557 filled_quantity: Some(match_result.order.filled_quantity), 558 status: Some(status), 559 })) 560 } 561 Ok(TradingResult::Failed { reason }) => Ok(Json(OrderResponse { 562 success: false, 563 message: reason, 564 order_id: None, 565 trades_executed: None, 566 filled_quantity: None, 567 status: None, 568 })), 569 Err(e) => Ok(Json(OrderResponse { 570 success: false, 571 message: e, 572 order_id: None, 573 trades_executed: None, 574 filled_quantity: None, 575 status: None, 576 })), 577 _ => Ok(Json(OrderResponse { 578 success: false, 579 message: "Unexpected result".to_string(), 580 order_id: None, 581 trades_executed: None, 582 filled_quantity: None, 583 status: None, 584 })), 585 } 586 } 587 588 /// POST /orders/market - Place a market order (F-D02) 589 async fn place_market_order( 590 State(state): State<Arc<RestState>>, 591 Json(req): Json<MarketOrderRequest>, 592 ) -> Result<Json<OrderResponse>, StatusCode> { 593 use deltavm_console::{Address, DeltaV0}; 594 use std::str::FromStr; 595 596 // Parse sender address 597 let sender: Address<DeltaV0> = Address::from_str(&req.sender).map_err(|_| { 598 tracing::warn!("Invalid sender address: {}", req.sender); 599 StatusCode::BAD_REQUEST 600 })?; 601 602 // Parse trading pair 603 let pair = parse_trading_pair(&req.pair).map_err(|e| { 604 tracing::warn!("Invalid trading pair: {}", e); 605 StatusCode::BAD_REQUEST 606 })?; 607 608 // Parse order side 609 let side = parse_order_side(&req.side).map_err(|e| { 610 tracing::warn!("Invalid order side: {}", e); 611 StatusCode::BAD_REQUEST 612 })?; 613 614 // Create trading transaction for market order 615 let tx = TradingTx::PlaceOrder { 616 pair, 617 side, 618 quantity: req.quantity, 619 price: None, // Market orders have no price limit 620 order_type: OrderType::Market, 621 }; 622 623 // Execute trading transaction 624 let mut runtime = state.runtime.write().await; 625 let result = runtime.execute_trading(sender, tx); 626 627 match result { 628 Ok(TradingResult::OrderPlaced { 629 match_result, 630 trades_executed, 631 }) => { 632 let order_id = hex::encode(&match_result.order.id.0[..16]); 633 let status = format!("{:?}", match_result.order.status); 634 635 Ok(Json(OrderResponse { 636 success: true, 637 message: format!( 638 "Market order executed. {} trades completed.", 639 trades_executed 640 ), 641 order_id: Some(order_id), 642 trades_executed: Some(trades_executed), 643 filled_quantity: Some(match_result.order.filled_quantity), 644 status: Some(status), 645 })) 646 } 647 Ok(TradingResult::Failed { reason }) => Ok(Json(OrderResponse { 648 success: false, 649 message: reason, 650 order_id: None, 651 trades_executed: None, 652 filled_quantity: None, 653 status: None, 654 })), 655 Err(e) => Ok(Json(OrderResponse { 656 success: false, 657 message: e, 658 order_id: None, 659 trades_executed: None, 660 filled_quantity: None, 661 status: None, 662 })), 663 _ => Ok(Json(OrderResponse { 664 success: false, 665 message: "Unexpected result".to_string(), 666 order_id: None, 667 trades_executed: None, 668 filled_quantity: None, 669 status: None, 670 })), 671 } 672 } 673 674 /// DELETE /orders/{id} - Cancel an order (F-D03) 675 async fn cancel_order( 676 State(state): State<Arc<RestState>>, 677 Path(order_id_str): Path<String>, 678 Json(req): Json<CancelOrderRequest>, 679 ) -> Result<Json<OrderResponse>, StatusCode> { 680 use deltavm_console::{Address, DeltaV0}; 681 use deltavm_execution::OrderId; 682 use std::str::FromStr; 683 684 // Parse sender address 685 let sender: Address<DeltaV0> = Address::from_str(&req.sender).map_err(|_| { 686 tracing::warn!("Invalid sender address: {}", req.sender); 687 StatusCode::BAD_REQUEST 688 })?; 689 690 // Parse order ID from hex string 691 let order_id_bytes = hex::decode(&order_id_str).map_err(|_| { 692 tracing::warn!("Invalid order ID format: {}", order_id_str); 693 StatusCode::BAD_REQUEST 694 })?; 695 696 if order_id_bytes.len() < 16 { 697 return Err(StatusCode::BAD_REQUEST); 698 } 699 700 // Reconstruct full order ID (pad with zeros if needed) 701 let mut order_id_array = [0u8; 32]; 702 let copy_len = order_id_bytes.len().min(32); 703 order_id_array[..copy_len].copy_from_slice(&order_id_bytes[..copy_len]); 704 let order_id = OrderId(order_id_array); 705 706 // Create cancel transaction 707 let tx = TradingTx::CancelOrder { order_id }; 708 709 // Execute cancel transaction 710 let mut runtime = state.runtime.write().await; 711 let result = runtime.execute_trading(sender, tx); 712 713 match result { 714 Ok(TradingResult::OrderCancelled { order }) => { 715 let cancelled_id = hex::encode(&order.id.0[..16]); 716 Ok(Json(OrderResponse { 717 success: true, 718 message: "Order cancelled successfully".to_string(), 719 order_id: Some(cancelled_id), 720 trades_executed: None, 721 filled_quantity: Some(order.filled_quantity), 722 status: Some("Cancelled".to_string()), 723 })) 724 } 725 Ok(TradingResult::Failed { reason }) => Ok(Json(OrderResponse { 726 success: false, 727 message: reason, 728 order_id: None, 729 trades_executed: None, 730 filled_quantity: None, 731 status: None, 732 })), 733 Err(e) => Ok(Json(OrderResponse { 734 success: false, 735 message: e, 736 order_id: None, 737 trades_executed: None, 738 filled_quantity: None, 739 status: None, 740 })), 741 _ => Ok(Json(OrderResponse { 742 success: false, 743 message: "Unexpected result".to_string(), 744 order_id: None, 745 trades_executed: None, 746 filled_quantity: None, 747 status: None, 748 })), 749 } 750 } 751 752 /// Query parameters for listing orders 753 #[derive(Deserialize)] 754 pub struct ListOrdersQuery { 755 /// User address to filter orders 756 pub address: String, 757 /// Optional pair filter 758 pub pair: Option<String>, 759 /// Optional status filter: "open", "filled", "cancelled", "all" 760 pub status: Option<String>, 761 } 762 763 /// GET /orders - List user orders 764 async fn list_user_orders( 765 State(state): State<Arc<RestState>>, 766 axum::extract::Query(query): axum::extract::Query<ListOrdersQuery>, 767 ) -> Result<Json<Vec<UserOrderResponse>>, StatusCode> { 768 // For now, return empty list - in production, would query from matching engine 769 // This would require extending the MatchingEngine to support order queries 770 let _address = &query.address; 771 772 // Get runtime to access matching engine 773 let runtime = state.runtime.read().await; 774 775 // Get order book snapshot for DELTA/ALPHA pair 776 let pair = if let Some(pair_str) = &query.pair { 777 parse_trading_pair(pair_str).unwrap_or(TradingPair::DeltaAlpha) 778 } else { 779 TradingPair::DeltaAlpha 780 }; 781 782 let snapshot = runtime.matching_engine.get_order_book_snapshot(&pair); 783 784 // Convert orders to response format 785 // Note: The current matching engine doesn't expose individual orders by user 786 // In a production system, we'd need to extend the matching engine 787 let orders: Vec<UserOrderResponse> = Vec::new(); 788 789 // Log for debugging 790 tracing::debug!( 791 "Listed orders for address {}: {} bids, {} asks in book", 792 query.address, 793 snapshot.bids.len(), 794 snapshot.asks.len() 795 ); 796 797 Ok(Json(orders)) 798 } 799 800 /// GET /orderbook/{pair} - Get order book for a trading pair (F-D06) 801 async fn get_order_book( 802 State(state): State<Arc<RestState>>, 803 Path(pair_str): Path<String>, 804 ) -> Result<Json<OrderBookResponse>, StatusCode> { 805 // Parse trading pair 806 let pair = parse_trading_pair(&pair_str).map_err(|e| { 807 tracing::warn!("Invalid trading pair: {}", e); 808 StatusCode::BAD_REQUEST 809 })?; 810 811 // Get order book snapshot from matching engine 812 let runtime = state.runtime.read().await; 813 let snapshot = runtime.matching_engine.get_order_book_snapshot(&pair); 814 815 // Convert to response format 816 let bids: Vec<OrderBookLevelResponse> = snapshot 817 .bids 818 .iter() 819 .map(|level| OrderBookLevelResponse { 820 price: level.price, 821 quantity: level.quantity, 822 order_count: level.order_count, 823 }) 824 .collect(); 825 826 let asks: Vec<OrderBookLevelResponse> = snapshot 827 .asks 828 .iter() 829 .map(|level| OrderBookLevelResponse { 830 price: level.price, 831 quantity: level.quantity, 832 order_count: level.order_count, 833 }) 834 .collect(); 835 836 Ok(Json(OrderBookResponse { 837 pair: format_trading_pair(&pair), 838 bids, 839 asks, 840 best_bid: snapshot.best_bid, 841 best_ask: snapshot.best_ask, 842 spread: snapshot.spread, 843 })) 844 } 845 846 /// GET /trades/{pair} - Get recent trades for a trading pair 847 async fn get_recent_trades( 848 State(_state): State<Arc<RestState>>, 849 Path(pair_str): Path<String>, 850 ) -> Result<Json<Vec<TradeResponse>>, StatusCode> { 851 // Parse trading pair 852 let _pair = parse_trading_pair(&pair_str).map_err(|e| { 853 tracing::warn!("Invalid trading pair: {}", e); 854 StatusCode::BAD_REQUEST 855 })?; 856 857 // For now, return empty list 858 // In production, would query trade history from storage 859 // The current implementation doesn't persist trade history 860 let trades: Vec<TradeResponse> = Vec::new(); 861 862 Ok(Json(trades)) 863 } 864 865 /// GET /balance/{address} - Get trading balance for an address 866 async fn get_balance( 867 State(state): State<Arc<RestState>>, 868 Path(address_str): Path<String>, 869 ) -> Result<Json<BalanceResponse>, StatusCode> { 870 use deltavm_console::{Address, DeltaV0}; 871 use std::str::FromStr; 872 873 // Parse address 874 let address: Address<DeltaV0> = Address::from_str(&address_str).map_err(|_| { 875 tracing::warn!("Invalid address: {}", address_str); 876 StatusCode::BAD_REQUEST 877 })?; 878 879 // Get balances from ledger 880 let runtime = state.runtime.read().await; 881 882 // Query balances for common assets 883 let delta_balance = runtime.ledger.balance(&address, "DELTA"); 884 let alpha_balance = runtime.ledger.balance(&address, "ALPHA"); 885 886 let balances = vec![ 887 AssetBalance { 888 asset: "DELTA".to_string(), 889 available: delta_balance, 890 locked: 0, // Would need order tracking to compute locked amounts 891 }, 892 AssetBalance { 893 asset: "ALPHA".to_string(), 894 available: alpha_balance, 895 locked: 0, 896 }, 897 ]; 898 899 Ok(Json(BalanceResponse { 900 address: address_str, 901 balances, 902 })) 903 } 904 905 // ============================================================================ 906 // Shared State Handlers (for exchange integration with shared runtime) 907 // ============================================================================ 908 909 /// Node status (shared state version) 910 async fn status_shared(State(state): State<Arc<RestStateShared>>) -> Json<StatusResponse> { 911 let node_state = state.node_state.read().await; 912 913 Json(StatusResponse { 914 version: "0.1.0".to_string(), 915 network: "DeltaV0".to_string(), 916 node_type: format!("{:?}", state.config.node_type), 917 block_height: node_state.block_height, 918 is_running: node_state.is_running, 919 is_synced: node_state.is_synced(), 920 peer_count: node_state.peer_count, 921 }) 922 } 923 924 /// Get latest block (shared state version) 925 async fn get_latest_block_shared(State(state): State<Arc<RestStateShared>>) -> Json<BlockResponse> { 926 let node_state = state.node_state.read().await; 927 let runtime = state.runtime.read().await; 928 929 Json(BlockResponse { 930 height: node_state.block_height, 931 hash: format!("delta_{:08x}", runtime.block_height), 932 timestamp: std::time::SystemTime::now() 933 .duration_since(std::time::UNIX_EPOCH) 934 .unwrap_or_default() 935 .as_secs(), 936 tx_count: 0, 937 }) 938 } 939 940 /// Get current block height (shared state version) 941 async fn get_block_height_shared(State(state): State<Arc<RestStateShared>>) -> String { 942 let node_state = state.node_state.read().await; 943 node_state.block_height.to_string() 944 } 945 946 /// Bridge status (shared state version) 947 async fn bridge_status_shared( 948 State(state): State<Arc<RestStateShared>>, 949 ) -> Json<BridgeStatusResponse> { 950 let runtime = state.runtime.read().await; 951 let stats = (*runtime).bridge_stats(); 952 953 Json(BridgeStatusResponse { 954 total_deposits: stats.total_deposits, 955 total_withdrawals: stats.total_withdrawals, 956 pending_withdrawals: stats.pending_withdrawals, 957 total_fees: stats.total_fees, 958 is_halted: stats.is_halted, 959 }) 960 } 961 962 /// Process deposit (shared state version) 963 async fn process_deposit_shared( 964 State(state): State<Arc<RestStateShared>>, 965 Json(req): Json<DepositRequest>, 966 ) -> Result<Json<TxResultResponse>, StatusCode> { 967 use deltavm_console::{Address, DeltaV0}; 968 use std::str::FromStr; 969 970 let recipient: Address<DeltaV0> = 971 Address::from_str(&req.recipient).map_err(|_| StatusCode::BAD_REQUEST)?; 972 973 let attestation = hex::decode(&req.attestation).unwrap_or_default(); 974 975 let tx = BridgeTx::LockAlpha { 976 attestation, 977 amount: req.amount, 978 recipient, 979 }; 980 981 let mut runtime = state.runtime.write().await; 982 let result = (*runtime).execute_bridge(tx); 983 984 match result { 985 BridgeTxResult::DepositSuccess { amount, fee } => Ok(Json(TxResultResponse { 986 success: true, 987 message: "Deposit processed successfully".to_string(), 988 amount: Some(amount), 989 fee: Some(fee), 990 nonce: None, 991 })), 992 BridgeTxResult::Failed { reason } => Ok(Json(TxResultResponse { 993 success: false, 994 message: reason, 995 amount: None, 996 fee: None, 997 nonce: None, 998 })), 999 _ => Ok(Json(TxResultResponse { 1000 success: false, 1001 message: "Unexpected result".to_string(), 1002 amount: None, 1003 fee: None, 1004 nonce: None, 1005 })), 1006 } 1007 } 1008 1009 /// Request withdrawal (shared state version) 1010 async fn request_withdrawal_shared( 1011 State(state): State<Arc<RestStateShared>>, 1012 Json(req): Json<WithdrawalRequest>, 1013 ) -> Result<Json<TxResultResponse>, StatusCode> { 1014 let tx = BridgeTx::UnlockAlpha { 1015 amount: req.amount, 1016 alpha_recipient: req.alpha_recipient.into_bytes(), 1017 }; 1018 1019 let mut runtime = state.runtime.write().await; 1020 let result = (*runtime).execute_bridge(tx); 1021 1022 match result { 1023 BridgeTxResult::WithdrawalAccepted { nonce, amount, fee } => Ok(Json(TxResultResponse { 1024 success: true, 1025 message: "Withdrawal request accepted".to_string(), 1026 amount: Some(amount), 1027 fee: Some(fee), 1028 nonce: Some(nonce), 1029 })), 1030 BridgeTxResult::Failed { reason } => Ok(Json(TxResultResponse { 1031 success: false, 1032 message: reason, 1033 amount: None, 1034 fee: None, 1035 nonce: None, 1036 })), 1037 _ => Ok(Json(TxResultResponse { 1038 success: false, 1039 message: "Unexpected result".to_string(), 1040 amount: None, 1041 fee: None, 1042 nonce: None, 1043 })), 1044 } 1045 } 1046 1047 /// POST /orders/limit - Place a limit order (shared state version) 1048 async fn place_limit_order_shared( 1049 State(state): State<Arc<RestStateShared>>, 1050 Json(req): Json<LimitOrderRequest>, 1051 ) -> Result<Json<OrderResponse>, StatusCode> { 1052 use deltavm_console::{Address, DeltaV0}; 1053 use std::str::FromStr; 1054 1055 let sender: Address<DeltaV0> = Address::from_str(&req.sender).map_err(|_| { 1056 tracing::warn!("Invalid sender address: {}", req.sender); 1057 StatusCode::BAD_REQUEST 1058 })?; 1059 1060 let pair = parse_trading_pair(&req.pair).map_err(|e| { 1061 tracing::warn!("Invalid trading pair: {}", e); 1062 StatusCode::BAD_REQUEST 1063 })?; 1064 1065 let side = parse_order_side(&req.side).map_err(|e| { 1066 tracing::warn!("Invalid order side: {}", e); 1067 StatusCode::BAD_REQUEST 1068 })?; 1069 1070 let tx = TradingTx::PlaceOrder { 1071 pair, 1072 side, 1073 quantity: req.quantity, 1074 price: Some(req.price), 1075 order_type: OrderType::Limit, 1076 }; 1077 1078 let mut runtime = state.runtime.write().await; 1079 let result = runtime.execute_trading(sender, tx); 1080 1081 match result { 1082 Ok(TradingResult::OrderPlaced { 1083 match_result, 1084 trades_executed, 1085 }) => { 1086 let order_id = hex::encode(&match_result.order.id.0[..16]); 1087 let status = format!("{:?}", match_result.order.status); 1088 1089 Ok(Json(OrderResponse { 1090 success: true, 1091 message: format!("Limit order placed. {} trades executed.", trades_executed), 1092 order_id: Some(order_id), 1093 trades_executed: Some(trades_executed), 1094 filled_quantity: Some(match_result.order.filled_quantity), 1095 status: Some(status), 1096 })) 1097 } 1098 Ok(TradingResult::Failed { reason }) => Ok(Json(OrderResponse { 1099 success: false, 1100 message: reason, 1101 order_id: None, 1102 trades_executed: None, 1103 filled_quantity: None, 1104 status: None, 1105 })), 1106 Err(e) => Ok(Json(OrderResponse { 1107 success: false, 1108 message: e, 1109 order_id: None, 1110 trades_executed: None, 1111 filled_quantity: None, 1112 status: None, 1113 })), 1114 _ => Ok(Json(OrderResponse { 1115 success: false, 1116 message: "Unexpected result".to_string(), 1117 order_id: None, 1118 trades_executed: None, 1119 filled_quantity: None, 1120 status: None, 1121 })), 1122 } 1123 } 1124 1125 /// POST /orders/market - Place a market order (shared state version) 1126 async fn place_market_order_shared( 1127 State(state): State<Arc<RestStateShared>>, 1128 Json(req): Json<MarketOrderRequest>, 1129 ) -> Result<Json<OrderResponse>, StatusCode> { 1130 use deltavm_console::{Address, DeltaV0}; 1131 use std::str::FromStr; 1132 1133 let sender: Address<DeltaV0> = Address::from_str(&req.sender).map_err(|_| { 1134 tracing::warn!("Invalid sender address: {}", req.sender); 1135 StatusCode::BAD_REQUEST 1136 })?; 1137 1138 let pair = parse_trading_pair(&req.pair).map_err(|e| { 1139 tracing::warn!("Invalid trading pair: {}", e); 1140 StatusCode::BAD_REQUEST 1141 })?; 1142 1143 let side = parse_order_side(&req.side).map_err(|e| { 1144 tracing::warn!("Invalid order side: {}", e); 1145 StatusCode::BAD_REQUEST 1146 })?; 1147 1148 let tx = TradingTx::PlaceOrder { 1149 pair, 1150 side, 1151 quantity: req.quantity, 1152 price: None, 1153 order_type: OrderType::Market, 1154 }; 1155 1156 let mut runtime = state.runtime.write().await; 1157 let result = runtime.execute_trading(sender, tx); 1158 1159 match result { 1160 Ok(TradingResult::OrderPlaced { 1161 match_result, 1162 trades_executed, 1163 }) => { 1164 let order_id = hex::encode(&match_result.order.id.0[..16]); 1165 let status = format!("{:?}", match_result.order.status); 1166 1167 Ok(Json(OrderResponse { 1168 success: true, 1169 message: format!( 1170 "Market order executed. {} trades completed.", 1171 trades_executed 1172 ), 1173 order_id: Some(order_id), 1174 trades_executed: Some(trades_executed), 1175 filled_quantity: Some(match_result.order.filled_quantity), 1176 status: Some(status), 1177 })) 1178 } 1179 Ok(TradingResult::Failed { reason }) => Ok(Json(OrderResponse { 1180 success: false, 1181 message: reason, 1182 order_id: None, 1183 trades_executed: None, 1184 filled_quantity: None, 1185 status: None, 1186 })), 1187 Err(e) => Ok(Json(OrderResponse { 1188 success: false, 1189 message: e, 1190 order_id: None, 1191 trades_executed: None, 1192 filled_quantity: None, 1193 status: None, 1194 })), 1195 _ => Ok(Json(OrderResponse { 1196 success: false, 1197 message: "Unexpected result".to_string(), 1198 order_id: None, 1199 trades_executed: None, 1200 filled_quantity: None, 1201 status: None, 1202 })), 1203 } 1204 } 1205 1206 /// DELETE /orders/{id} - Cancel an order (shared state version) 1207 async fn cancel_order_shared( 1208 State(state): State<Arc<RestStateShared>>, 1209 Path(order_id_str): Path<String>, 1210 Json(req): Json<CancelOrderRequest>, 1211 ) -> Result<Json<OrderResponse>, StatusCode> { 1212 use deltavm_console::{Address, DeltaV0}; 1213 use deltavm_execution::OrderId; 1214 use std::str::FromStr; 1215 1216 let sender: Address<DeltaV0> = Address::from_str(&req.sender).map_err(|_| { 1217 tracing::warn!("Invalid sender address: {}", req.sender); 1218 StatusCode::BAD_REQUEST 1219 })?; 1220 1221 let order_id_bytes = hex::decode(&order_id_str).map_err(|_| { 1222 tracing::warn!("Invalid order ID format: {}", order_id_str); 1223 StatusCode::BAD_REQUEST 1224 })?; 1225 1226 if order_id_bytes.len() < 16 { 1227 return Err(StatusCode::BAD_REQUEST); 1228 } 1229 1230 let mut order_id_array = [0u8; 32]; 1231 let copy_len = order_id_bytes.len().min(32); 1232 order_id_array[..copy_len].copy_from_slice(&order_id_bytes[..copy_len]); 1233 let order_id = OrderId(order_id_array); 1234 1235 let tx = TradingTx::CancelOrder { order_id }; 1236 1237 let mut runtime = state.runtime.write().await; 1238 let result = runtime.execute_trading(sender, tx); 1239 1240 match result { 1241 Ok(TradingResult::OrderCancelled { order }) => { 1242 let cancelled_id = hex::encode(&order.id.0[..16]); 1243 Ok(Json(OrderResponse { 1244 success: true, 1245 message: "Order cancelled successfully".to_string(), 1246 order_id: Some(cancelled_id), 1247 trades_executed: None, 1248 filled_quantity: Some(order.filled_quantity), 1249 status: Some("Cancelled".to_string()), 1250 })) 1251 } 1252 Ok(TradingResult::Failed { reason }) => Ok(Json(OrderResponse { 1253 success: false, 1254 message: reason, 1255 order_id: None, 1256 trades_executed: None, 1257 filled_quantity: None, 1258 status: None, 1259 })), 1260 Err(e) => Ok(Json(OrderResponse { 1261 success: false, 1262 message: e, 1263 order_id: None, 1264 trades_executed: None, 1265 filled_quantity: None, 1266 status: None, 1267 })), 1268 _ => Ok(Json(OrderResponse { 1269 success: false, 1270 message: "Unexpected result".to_string(), 1271 order_id: None, 1272 trades_executed: None, 1273 filled_quantity: None, 1274 status: None, 1275 })), 1276 } 1277 } 1278 1279 /// GET /orders - List user orders (shared state version) 1280 async fn list_user_orders_shared( 1281 State(state): State<Arc<RestStateShared>>, 1282 axum::extract::Query(query): axum::extract::Query<ListOrdersQuery>, 1283 ) -> Result<Json<Vec<UserOrderResponse>>, StatusCode> { 1284 let _address = &query.address; 1285 let runtime = state.runtime.read().await; 1286 1287 let pair = if let Some(pair_str) = &query.pair { 1288 parse_trading_pair(pair_str).unwrap_or(TradingPair::DeltaAlpha) 1289 } else { 1290 TradingPair::DeltaAlpha 1291 }; 1292 1293 let snapshot = runtime.matching_engine.get_order_book_snapshot(&pair); 1294 let orders: Vec<UserOrderResponse> = Vec::new(); 1295 1296 tracing::debug!( 1297 "Listed orders for address {}: {} bids, {} asks in book", 1298 query.address, 1299 snapshot.bids.len(), 1300 snapshot.asks.len() 1301 ); 1302 1303 Ok(Json(orders)) 1304 } 1305 1306 /// GET /orderbook/{pair} - Get order book (shared state version) 1307 async fn get_order_book_shared( 1308 State(state): State<Arc<RestStateShared>>, 1309 Path(pair_str): Path<String>, 1310 ) -> Result<Json<OrderBookResponse>, StatusCode> { 1311 let pair = parse_trading_pair(&pair_str).map_err(|e| { 1312 tracing::warn!("Invalid trading pair: {}", e); 1313 StatusCode::BAD_REQUEST 1314 })?; 1315 1316 let runtime = state.runtime.read().await; 1317 let snapshot = runtime.matching_engine.get_order_book_snapshot(&pair); 1318 1319 let bids: Vec<OrderBookLevelResponse> = snapshot 1320 .bids 1321 .iter() 1322 .map(|level| OrderBookLevelResponse { 1323 price: level.price, 1324 quantity: level.quantity, 1325 order_count: level.order_count, 1326 }) 1327 .collect(); 1328 1329 let asks: Vec<OrderBookLevelResponse> = snapshot 1330 .asks 1331 .iter() 1332 .map(|level| OrderBookLevelResponse { 1333 price: level.price, 1334 quantity: level.quantity, 1335 order_count: level.order_count, 1336 }) 1337 .collect(); 1338 1339 Ok(Json(OrderBookResponse { 1340 pair: format_trading_pair(&pair), 1341 bids, 1342 asks, 1343 best_bid: snapshot.best_bid, 1344 best_ask: snapshot.best_ask, 1345 spread: snapshot.spread, 1346 })) 1347 } 1348 1349 /// GET /trades/{pair} - Get recent trades (shared state version) 1350 async fn get_recent_trades_shared( 1351 State(_state): State<Arc<RestStateShared>>, 1352 Path(pair_str): Path<String>, 1353 ) -> Result<Json<Vec<TradeResponse>>, StatusCode> { 1354 let _pair = parse_trading_pair(&pair_str).map_err(|e| { 1355 tracing::warn!("Invalid trading pair: {}", e); 1356 StatusCode::BAD_REQUEST 1357 })?; 1358 1359 let trades: Vec<TradeResponse> = Vec::new(); 1360 Ok(Json(trades)) 1361 } 1362 1363 /// GET /balance/{address} - Get trading balance (shared state version) 1364 async fn get_balance_shared( 1365 State(state): State<Arc<RestStateShared>>, 1366 Path(address_str): Path<String>, 1367 ) -> Result<Json<BalanceResponse>, StatusCode> { 1368 use deltavm_console::{Address, DeltaV0}; 1369 use std::str::FromStr; 1370 1371 let address: Address<DeltaV0> = Address::from_str(&address_str).map_err(|_| { 1372 tracing::warn!("Invalid address: {}", address_str); 1373 StatusCode::BAD_REQUEST 1374 })?; 1375 1376 let runtime = state.runtime.read().await; 1377 1378 let delta_balance = runtime.ledger.balance(&address, "DELTA"); 1379 let alpha_balance = runtime.ledger.balance(&address, "ALPHA"); 1380 1381 let balances = vec![ 1382 AssetBalance { 1383 asset: "DELTA".to_string(), 1384 available: delta_balance, 1385 locked: 0, 1386 }, 1387 AssetBalance { 1388 asset: "ALPHA".to_string(), 1389 available: alpha_balance, 1390 locked: 0, 1391 }, 1392 ]; 1393 1394 Ok(Json(BalanceResponse { 1395 address: address_str, 1396 balances, 1397 })) 1398 }