/ node / src / rest.rs
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  }