Order RPCs

gRPC API for order placement, cancellation, and management

Order RPCs

Complete reference for order management via gRPC: PlaceOrder, CancelOrder, GetOrder, and GetOrders.

Proto Definitions

Order Message

message Order {
  uint64 order_id = 1;           // Exchange-assigned unique ID
  string symbol = 2;             // Trading pair (e.g., "LUX-USDC")
  OrderType type = 3;            // Order type
  OrderSide side = 4;            // BUY or SELL
  double price = 5;              // Limit price
  double size = 6;               // Original quantity
  double filled = 7;             // Quantity filled
  double remaining = 8;          // Quantity remaining
  OrderStatus status = 9;        // Current status
  string user_id = 10;           // Owner user ID
  string client_id = 11;         // Client-assigned ID (optional)
  int64 timestamp = 12;          // Creation time (Unix nanoseconds)
  TimeInForce time_in_force = 13; // Time validity
  bool post_only = 14;           // Maker-only flag
  bool reduce_only = 15;         // Position reduction only
}

Enums

enum OrderType {
  LIMIT = 0;       // Standard limit order
  MARKET = 1;      // Execute immediately at best price
  STOP = 2;        // Market order triggered at stop_price
  STOP_LIMIT = 3;  // Limit order triggered at stop_price
  ICEBERG = 4;     // Large order with visible portion
  PEG = 5;         // Price pegged to reference
}

enum OrderSide {
  BUY = 0;   // Bid (buy base, pay quote)
  SELL = 1;  // Ask (sell base, receive quote)
}

enum OrderStatus {
  OPEN = 0;       // Active in order book
  PARTIAL = 1;    // Partially filled, remaining in book
  FILLED = 2;     // Completely filled
  CANCELLED = 3;  // Cancelled by user
  REJECTED = 4;   // Rejected by matching engine
}

enum TimeInForce {
  GTC = 0;  // Good Till Cancelled (default)
  IOC = 1;  // Immediate Or Cancel
  FOK = 2;  // Fill Or Kill
  DAY = 3;  // Expires at 00:00 UTC
}

PlaceOrder

Submit a new order to the matching engine.

RPC Definition

rpc PlaceOrder(PlaceOrderRequest) returns (PlaceOrderResponse);

Request

message PlaceOrderRequest {
  string symbol = 1;            // Required: trading pair
  OrderType type = 2;           // Required: order type
  OrderSide side = 3;           // Required: BUY or SELL
  double price = 4;             // Required for LIMIT/STOP_LIMIT
  double size = 5;              // Required: quantity
  string user_id = 6;           // Required: user identifier
  string client_id = 7;         // Optional: idempotency key
  TimeInForce time_in_force = 8; // Optional: defaults to GTC
  bool post_only = 9;           // Optional: reject if taker
  bool reduce_only = 10;        // Optional: reduce position only
  double stop_price = 11;       // Required for STOP/STOP_LIMIT
  double limit_price = 12;      // Required for STOP_LIMIT
}

Response

message PlaceOrderResponse {
  uint64 order_id = 1;    // Exchange order ID (0 if rejected)
  OrderStatus status = 2; // OPEN, PARTIAL, FILLED, or REJECTED
  string message = 3;     // Status details or rejection reason
}

Field Validation

FieldValidation
symbolMust exist in supported markets
price> 0, precision <= 8 decimals
size>= min_order_size, precision <= 8 decimals
user_idNon-empty, valid user
client_idMax 64 characters, unique per user within 24h
stop_price> 0 for STOP/STOP_LIMIT orders

Error Codes

gRPC CodeCondition
INVALID_ARGUMENTInvalid symbol, price, or size
FAILED_PRECONDITIONInsufficient balance
ALREADY_EXISTSDuplicate client_id
RESOURCE_EXHAUSTEDRate limit exceeded
UNAVAILABLEMatching engine unavailable

Examples

Go: Limit Order

resp, err := client.PlaceOrder(ctx, &pb.PlaceOrderRequest{
    Symbol:      "LUX-USDC",
    Type:        pb.OrderType_LIMIT,
    Side:        pb.OrderSide_BUY,
    Price:       100.50,
    Size:        10.0,
    UserId:      "user123",
    ClientId:    "order-001",
    TimeInForce: pb.TimeInForce_GTC,
})
if err != nil {
    st, _ := status.FromError(err)
    return fmt.Errorf("place order failed: %s", st.Message())
}

switch resp.Status {
case pb.OrderStatus_OPEN:
    log.Printf("Order %d placed in book", resp.OrderId)
case pb.OrderStatus_PARTIAL:
    log.Printf("Order %d partially filled", resp.OrderId)
case pb.OrderStatus_FILLED:
    log.Printf("Order %d completely filled", resp.OrderId)
case pb.OrderStatus_REJECTED:
    log.Printf("Order rejected: %s", resp.Message)
}

Go: Market Order

resp, err := client.PlaceOrder(ctx, &pb.PlaceOrderRequest{
    Symbol:      "LUX-USDC",
    Type:        pb.OrderType_MARKET,
    Side:        pb.OrderSide_SELL,
    Size:        5.0,
    UserId:      "user123",
    TimeInForce: pb.TimeInForce_IOC,
})

Go: Stop-Limit Order

resp, err := client.PlaceOrder(ctx, &pb.PlaceOrderRequest{
    Symbol:     "LUX-USDC",
    Type:       pb.OrderType_STOP_LIMIT,
    Side:       pb.OrderSide_SELL,
    Size:       10.0,
    StopPrice:  95.00,   // Trigger price
    LimitPrice: 94.50,   // Execution price
    UserId:     "user123",
})

Python: Post-Only Order

response = stub.PlaceOrder(lxdex_pb2.PlaceOrderRequest(
    symbol="LUX-USDC",
    type=lxdex_pb2.LIMIT,
    side=lxdex_pb2.BUY,
    price=99.00,
    size=100.0,
    user_id="user123",
    post_only=True,  # Reject if would cross spread
))

if response.status == lxdex_pb2.REJECTED:
    print(f"Post-only rejected (would have taken): {response.message}")

CancelOrder

Cancel an existing active order.

RPC Definition

rpc CancelOrder(CancelOrderRequest) returns (CancelOrderResponse);

Request

message CancelOrderRequest {
  uint64 order_id = 1;  // Required: order to cancel
  string user_id = 2;   // Required: must match order owner
}

Response

message CancelOrderResponse {
  bool success = 1;    // True if cancelled
  string message = 2;  // Status message
}

Error Codes

gRPC CodeCondition
NOT_FOUNDOrder does not exist
PERMISSION_DENIEDuser_id mismatch
ABORTEDOrder already filled/cancelled

Examples

Go

resp, err := client.CancelOrder(ctx, &pb.CancelOrderRequest{
    OrderId: 12345,
    UserId:  "user123",
})
if err != nil {
    st, _ := status.FromError(err)
    switch st.Code() {
    case codes.NotFound:
        log.Println("Order not found")
    case codes.PermissionDenied:
        log.Println("Not your order")
    case codes.Aborted:
        log.Println("Order already terminal")
    default:
        log.Printf("Cancel failed: %s", st.Message())
    }
    return
}

if resp.Success {
    log.Printf("Cancelled: %s", resp.Message)
}

Python

try:
    response = stub.CancelOrder(lxdex_pb2.CancelOrderRequest(
        order_id=12345,
        user_id="user123",
    ))
    if response.success:
        print(f"Cancelled: {response.message}")
except grpc.RpcError as e:
    if e.code() == grpc.StatusCode.NOT_FOUND:
        print("Order not found")
    elif e.code() == grpc.StatusCode.ABORTED:
        print("Order already filled or cancelled")

GetOrder

Retrieve a single order by ID.

RPC Definition

rpc GetOrder(GetOrderRequest) returns (Order);

Request

message GetOrderRequest {
  uint64 order_id = 1;  // Required: order ID
}

Response

Returns the full Order message.

Error Codes

gRPC CodeCondition
NOT_FOUNDOrder does not exist

Examples

Go

order, err := client.GetOrder(ctx, &pb.GetOrderRequest{
    OrderId: 12345,
})
if err != nil {
    st, _ := status.FromError(err)
    if st.Code() == codes.NotFound {
        log.Println("Order not found")
        return
    }
    log.Fatal(err)
}

log.Printf("Order %d: %s %s %.4f @ %.2f [%s]",
    order.OrderId,
    order.Side,
    order.Symbol,
    order.Size,
    order.Price,
    order.Status,
)
log.Printf("  Filled: %.4f / Remaining: %.4f",
    order.Filled,
    order.Remaining,
)

Python

try:
    order = stub.GetOrder(lxdex_pb2.GetOrderRequest(order_id=12345))
    print(f"Order {order.order_id}: {order.side} {order.size} @ {order.price}")
    print(f"Status: {order.status}, Filled: {order.filled}")
except grpc.RpcError as e:
    if e.code() == grpc.StatusCode.NOT_FOUND:
        print("Order not found")

GetOrders

Query multiple orders with filters.

RPC Definition

rpc GetOrders(GetOrdersRequest) returns (GetOrdersResponse);

Request

message GetOrdersRequest {
  string user_id = 1;       // Required: filter by user
  string symbol = 2;        // Optional: filter by symbol
  OrderStatus status = 3;   // Optional: filter by status
  int32 limit = 4;          // Optional: max results (default 100, max 1000)
}

Response

message GetOrdersResponse {
  repeated Order orders = 1;  // Ordered by timestamp descending
}

Examples

Go: Get All Open Orders

resp, err := client.GetOrders(ctx, &pb.GetOrdersRequest{
    UserId: "user123",
    Status: pb.OrderStatus_OPEN,
    Limit:  100,
})
if err != nil {
    log.Fatal(err)
}

log.Printf("Found %d open orders:", len(resp.Orders))
for _, order := range resp.Orders {
    log.Printf("  %d: %s %s %.4f @ %.2f",
        order.OrderId,
        order.Side,
        order.Symbol,
        order.Remaining,
        order.Price,
    )
}

Go: Get Orders for Specific Symbol

resp, err := client.GetOrders(ctx, &pb.GetOrdersRequest{
    UserId: "user123",
    Symbol: "LUX-USDC",
    Limit:  50,
})

Python: Get Recent Orders

response = stub.GetOrders(lxdex_pb2.GetOrdersRequest(
    user_id="user123",
    limit=20,
))

for order in response.orders:
    status_name = lxdex_pb2.OrderStatus.Name(order.status)
    print(f"{order.order_id}: {status_name} - {order.side} {order.size} @ {order.price}")

Order Lifecycle

                    ┌──────────────┐
                    │  PlaceOrder  │
                    └──────┬───────┘

                    ┌──────▼───────┐
              ┌─────│   OPEN       │─────┐
              │     └──────┬───────┘     │
         Cancel│           │Match        │ FOK/IOC
              │     ┌──────▼───────┐     │ reject
              │     │   PARTIAL    │     │
              │     └──────┬───────┘     │
              │            │             │
       ┌──────▼───────┐    │      ┌──────▼───────┐
       │  CANCELLED   │    │      │   REJECTED   │
       └──────────────┘    │      └──────────────┘
                    ┌──────▼───────┐
                    │    FILLED    │
                    └──────────────┘

Status Transitions

FromToTrigger
(new)OPENOrder enters book
(new)PARTIALPartial immediate fill
(new)FILLEDComplete immediate fill
(new)REJECTEDValidation failure, post-only cross, FOK not fillable
OPENPARTIALPartial match
OPENFILLEDComplete match
OPENCANCELLEDUser cancellation
PARTIALFILLEDRemaining filled
PARTIALCANCELLEDUser cancellation

Best Practices

1. Use Client IDs for Idempotency

// Generate unique client ID
clientID := fmt.Sprintf("%s-%d", userID, time.Now().UnixNano())

resp, err := client.PlaceOrder(ctx, &pb.PlaceOrderRequest{
    // ...
    ClientId: clientID,
})

// On network error, safe to retry with same client_id
// Server will return existing order if duplicate

2. Handle Partial Fills

// Subscribe to order updates (via WebSocket or polling)
// to track fill progress

order, _ := client.GetOrder(ctx, &pb.GetOrderRequest{OrderId: orderID})

fillPercentage := order.Filled / order.Size * 100
log.Printf("Order %.1f%% filled", fillPercentage)

if fillPercentage > 50 && order.Status == pb.OrderStatus_PARTIAL {
    // Consider leaving remaining on book vs cancelling
}

3. Post-Only for Market Making

// Use post-only to ensure maker rebates
resp, err := client.PlaceOrder(ctx, &pb.PlaceOrderRequest{
    Symbol:   "LUX-USDC",
    Type:     pb.OrderType_LIMIT,
    Side:     pb.OrderSide_BUY,
    Price:    bestBid - 0.01, // Below best bid
    Size:     100.0,
    UserId:   "mm-bot",
    PostOnly: true,
})

if resp.Status == pb.OrderStatus_REJECTED {
    // Price crossed spread, adjust and retry
}

4. Batch Order Management

// Cancel all open orders for a symbol
orders, _ := client.GetOrders(ctx, &pb.GetOrdersRequest{
    UserId: "user123",
    Symbol: "LUX-USDC",
    Status: pb.OrderStatus_OPEN,
})

var wg sync.WaitGroup
for _, order := range orders.Orders {
    wg.Add(1)
    go func(id uint64) {
        defer wg.Done()
        client.CancelOrder(ctx, &pb.CancelOrderRequest{
            OrderId: id,
            UserId:  "user123",
        })
    }(order.OrderId)
}
wg.Wait()

Rate Limits

RPCRate LimitBurst
PlaceOrder100/s per user200
CancelOrder100/s per user200
GetOrder1000/s per user2000
GetOrders100/s per user200

Exceeding limits returns RESOURCE_EXHAUSTED with Retry-After metadata.