syntax = "proto3";

package xyz.aspens.arborter.v1;

option optimize_for = SPEED;

service ArborterService {
  rpc SendOrder(SendOrderRequest) returns (SendOrderResponse) {}
  rpc CancelOrder(CancelOrderRequest) returns (CancelOrderResponse) {}
  rpc Trades(TradeRequest) returns (stream Trade) {}
  rpc Orderbook(OrderbookRequest) returns (stream OrderbookEntry) {}
  // Request a TEE-signed withdrawal voucher (Track A §8). The chain can no
  // longer self-judge a withdrawal under the optimistic shadow ledger, so the
  // arborter freezes the settled funds and returns an owner-signed voucher the
  // holder submits to MidribV2.withdraw(voucher, signature).
  rpc Withdraw(WithdrawRequest) returns (WithdrawResponse) {}
}

message Empty {}

/* rpc: Withdraw */
message WithdrawRequest {
  // The chain/network the funds sit on (network key, e.g. "flare-coston2").
  string network = 1;
  // Token contract address on `network`.
  string token = 2;
  // The withdrawer's wallet address on `network`. The resulting voucher pays
  // this address regardless of who submits it on-chain.
  string account = 3;
  // Amount in token base units, as a decimal string (u128).
  string amount = 4;
  // Signature by `account`'s key over the canonical request bytes
  // ("network|token|account|amount"): Secp256k1 on EVM, Ed25519 on Solana.
  bytes signature = 5;
}

message WithdrawResponse {
  // The voucher fields the holder submits to MidribV2.withdraw(...).
  string account = 1;
  string token = 2;
  string amount = 3;
  uint64 nonce = 4;
  uint64 expiry = 5;
  // The TEE (instance-owner) EIP-712 signature over the voucher.
  bytes signature = 6;
}

message CancelOrderRequest {
  OrderToCancel order = 1;
  bytes signature_hash = 2;
}

message CancelOrderResponse {
  // Whether the order was found and canceled
  bool order_canceled = 1;
  // Transaction hashes associated with this cancellation
  repeated TransactionHash transaction_hashes = 2;
  // Current state of the orderbook after this operation
  repeated OrderbookEntry current_orderbook = 3;
}

message OrderbookRequest {
  // Used to keep the client alive
  bool continue_stream = 1;
  // The market id to stream orders for
  string market_id = 2;
  // If true, returns existing open orders when stream starts, then continues streaming
  optional bool historical_open_orders = 3;
  // If this is sent too, then we filter by a specific trader
  optional string filter_by_trader = 4;
}

message TradeRequest {
    // Used to keep the client alive
    bool continue_stream = 1;
    // The market id to stream orders for
    string market_id = 2;
    // If true, returns existing closed when stream starts, then continues streaming
    optional bool historical_closed_trades = 3;
    // If this is sent too, then we filter by a specific trader
    optional string filter_by_trader = 4;
}

message SendOrderRequest {
  // The order to send
  Order order = 1;
  // Valid EIP-712 signature hash of this order
  bytes signature_hash = 2;
  // Order authorization carrying the SDK-derived canonical order id and the
  // committed lock amount. Required. For message-typed fields proto3 tracks
  // presence by default, so the generated Rust API is
  // `Option<OrderAuthorization>`; the arborter handler enforces presence at
  // the request boundary.
  OrderAuthorization authorization = 3;
}

// SDK-derived order authorization. Under the optimistic shadow ledger, order
// entry never touches the chain — the arborter authenticates the order via the
// outer envelope signature (`SendOrderRequest.signature_hash`) and consumes
// only the two fields below. The legacy gasless on-chain-lock fields
// (user_signature / deadline / nonce / open_deadline / amount_out) were removed
// with the on-chain order machinery; the message and its `SendOrderRequest`
// field were renamed from `GaslessAuthorization` / `gasless` to match.
message OrderAuthorization {
  // The canonical on-chain order id (32-byte hex, 0x-prefixed), derived by the
  // SDK (`aspens::orders::derive_order_id`). The arborter uses it verbatim as
  // the order's id throughout match / settle, so it MUST match the SDK's
  // derivation exactly.
  string order_id = 1;
  // The committed lock amount on the origin chain (input token), in that
  // token's native base units (NOT pair decimals). Decimal-string encoding of
  // `u128` (no `0x` prefix, no separators) so values up to 2^128-1 round-trip
  // exactly across the gRPC boundary — protobuf has no native 128-bit integer.
  string amount_in = 2;
}

message Order {
  // 'BID' or 'ASK'
  Side side = 1;
  // Order size
  string quantity = 2;
  // Optional. including is a LIMIT order. excluding is a MARKET order.
  optional string price = 3;
  // Identity the market: concat(base_chain_id "::" token_address "::" quote_chain_id "::" token_address)
  string market_id = 4;
  // User's pubkey (address) on the Base chain
  string base_account_address = 5;
  // User's pubkey (address) on the Quote chain
  string quote_account_address = 6;
  // 'DIRECT' (default) or 'DISCRETIONARY'
  ExecutionType execution_type = 7;
  // When execution_type == 'discretionary', include order_ids to match with.
  repeated uint64 matching_order_ids = 8;
  // Post-only: if true, the order MUST rest on the book. If it would
  // cross any opposing confirmed order at submission time the request
  // is rejected with FAILED_PRECONDITION and no on-chain lock is
  // performed. Limit orders only — incompatible with market (no price)
  // and with DISCRETIONARY execution_type. Defaults to false, which
  // preserves legacy take-or-rest behavior.
  bool post_only = 9;
}

enum Side {
  SIDE_UNSPECIFIED = 0;
  SIDE_BID = 1;
  SIDE_ASK = 2;
}

enum ExecutionType {
  // Default - direct
  EXECUTION_TYPE_UNSPECIFIED = 0;
  // For dealroom use.
  EXECUTION_TYPE_DISCRETIONARY = 1;
}

// The actual state of an order in the matching engine
enum OrderState {
  ORDER_STATE_UNSPECIFIED = 0;
  // Order submitted, funds locked on-chain, shows in orderbook but NOT matchable
  ORDER_STATE_PENDING = 1;
  // OrderOpen blockchain event received, order is now matchable
  ORDER_STATE_CONFIRMED = 2;
  // Order matched by matching engine, trade recorded
  ORDER_STATE_MATCHED = 3;
  // Order canceled
  ORDER_STATE_CANCELED = 4;
  // On-chain settlement completed successfully
  ORDER_STATE_SETTLED = 5;
}

message Trade {
  // When the engine registers this executed trade
  uint64 timestamp = 1;
  // The setttled price net of fees
  string price = 2;
  // How much or many of the quote token
  string qty = 3;
  // Maker's internal trader id. Safely ignore.
  string maker_id = 4;
  // Taker's internal trader id. Safely ignore.
  string taker_id = 5;
  // The maker's base chain wallet address
  string maker_base_address = 6;
  // The maker's quote chain wallet address
  string maker_quote_address = 7;
  // The taker's base chain wallet address
  string taker_base_address = 8;
  // The taker's quote chain wallet address
  string taker_quote_address = 9;
  // Who is the buyer in this trade (MAKER or TAKER)
  TradeRole buyer_is = 10;
  // Who is the seller in this trade (MAKER or TAKER)
  TradeRole seller_is = 11;
  // The order_id that created this trade.
  uint64 order_hit = 12;
}

enum TradeRole {
  TRADE_ROLE_UNSPECIFIED = 0;
  MAKER = 1;
  TAKER = 2;
}

/* Transaction hash information */
message TransactionHash {
  // Type of transaction hash
  string hash_type = 1;
  // The transaction hash value
  string hash_value = 2;
}

/* rpc: SendOrder */
message SendOrderResponse {
  // Whether the order has unmatched volume and has been added to the book.
  bool order_in_book = 1;
  // If the order lands in the book, what remains unfilled from the order.
  optional Order order = 2;
  // Which other orders, if any, this order was matched with.
  repeated Trade trades = 3;
  // Transaction hashes associated with this order
  repeated TransactionHash transaction_hashes = 4;
  // Current state of the orderbook after this operation
  repeated OrderbookEntry current_orderbook = 5;
  // The unique identifier for this order
  uint64 order_id = 6;
}

/* rpc: CancelOrder */
message OrderToCancel {
  // Identity the market: concat(base_chain_id "::" token_address "::" quote_chain_id "::" token_address)
  string market_id = 1;
  // 'BID' or 'ASK'
  Side side = 2;
  // the token address
  string token_address = 3;
  // Internal order Id.
  uint64 order_id = 4;
}

/* rpc: Orderbook */
message OrderbookEntry {
  // when the order first landed in The Orderbook
  uint64 timestamp = 1;
  // internal id of the order
  uint64 order_id = 2;
  // price of the order - in non-decimal form
  string price = 3;
  // quantity of the order - in non-decimal form
  string quantity = 4;
  // 'BID = 1' or 'ASK = 2'
  Side side = 5;
  // The maker's base chain wallet address
  string maker_base_address = 6;
  // The maker's quote chain wallet address
  string maker_quote_address = 7;
  // The market ID this order belongs to
  string market_id = 8;
  // The actual state of the order (Pending, Confirmed, Matched, Canceled, Settled)
  OrderState state = 9;
  // Mirrors Order.post_only for the resting order. Lets streaming
  // clients distinguish post-only liquidity from regular limits.
  bool post_only = 10;
}
