// Code generated by `make bundle-margin-proto`; DO NOT EDIT.
// Services are grouped first so the RPC surface is easy to scan.

syntax = "proto3";

package flint.margin.user.v1;

// -- account/service.proto --------------------------------------------------------

service AccountService {
  rpc BatchGetAccounts(BatchGetAccountsRequest) returns (BatchGetAccountsResponse);
  rpc SubscribeAccounts(SubscribeAccountsRequest) returns (stream SubscribeAccountsResponse);
  rpc ListDelegates(ListDelegatesRequest) returns (ListDelegatesResponse);
  rpc SubscribeActivity(SubscribeActivityRequest) returns (stream ActivityEntry);
  rpc GetInterestHistory(GetInterestHistoryRequest) returns (GetInterestHistoryResponse);
  rpc GetPositionHistory(GetPositionHistoryRequest) returns (GetPositionHistoryResponse);
  rpc GetFills(GetFillsRequest) returns (GetFillsResponse);
  rpc SubscribeFills(SubscribeFillsRequest) returns (stream Fill);
  rpc GetTradingLimits(GetTradingLimitsRequest) returns (GetTradingLimitsResponse);
  rpc GetTradingFees(GetTradingFeesRequest) returns (GetTradingFeesResponse);
  rpc ListAccounts(ListAccountsRequest) returns (ListAccountsResponse);
  rpc GetRankedAccounts(GetRankedAccountsRequest) returns (GetRankedAccountsResponse);
  rpc GetAccount(GetAccountRequest) returns (GetAccountResponse);
  rpc SubscribeAccount(SubscribeAccountRequest) returns (stream AccountSnapshot);
  rpc GetActionLimits(GetActionLimitsRequest) returns (GetActionLimitsResponse);
  rpc SimulateAction(SimulateActionRequest) returns (SimulateActionResponse);
  rpc GetActivity(GetActivityRequest) returns (GetActivityResponse);
  rpc GetAccountHistory(GetAccountHistoryRequest) returns (GetAccountHistoryResponse);
  rpc RenameAccount(RenameAccountRequest) returns (RenameAccountResponse);
}

// -- action/service.proto --------------------------------------------------------

service ActionService {
  // Checks a batch of trader actions (orders, closes, cancels, settles,
  // deposits, withdrawals, transfers, lending) for one margin account and
  // returns a link to the trading app's review page, where the account owner
  // reviews, excludes and signs them. Builds, signs, and sends nothing: the
  // link carries intent only and grants nothing, and the app checks
  // everything again before signing.
  //
  // Each action is checked alone against the account as it stands now;
  // balances and positions are not chained through the batch. A deposit that
  // funds an order, or two withdrawals that together overdraw, can each come
  // back READY. Actions run in steps, keeping the request's order within one:
  // 1 deposits; 2 cancels, settles, lends, repays and borrows; 3 orders and
  // closes; 4 withdrawals, transfers and redeems.
  //
  // Malformed input is INVALID_ARGUMENT, naming `actions[i].field`; problems
  // with live state are BLOCKED rows. If any row is BLOCKED there is no link:
  // fix or drop those actions and preview again. The link is also empty, with
  // a warning, when no trading app is deployed for this cluster.
  rpc PreviewActions(PreviewActionsRequest) returns (PreviewActionsResponse);
}

// -- funding/service.proto --------------------------------------------------------

service FundingService {
  rpc GetFundingRates(GetFundingRatesRequest) returns (GetFundingRatesResponse);
  rpc SubscribeFundingRates(SubscribeFundingRatesRequest) returns (stream SubscribeFundingRatesResponse);
  rpc GetFundingRateHistory(GetFundingRateHistoryRequest) returns (GetFundingRateHistoryResponse);
  rpc GetFundingPayments(GetFundingPaymentsRequest) returns (GetFundingPaymentsResponse);
  rpc SubscribeFundingPayments(SubscribeFundingPaymentsRequest) returns (stream SubscribeFundingPaymentsResponse);
}

// -- market/service.proto --------------------------------------------------------

// Public catalog and live state of lending markets.
service MarketService {
  rpc GetMarket(GetMarketRequest) returns (GetMarketResponse);
  rpc GetMarketHistory(GetMarketHistoryRequest) returns (GetMarketHistoryResponse);
  rpc ListMarkets(ListMarketsRequest) returns (ListMarketsResponse);
  rpc SubscribeMarkets(SubscribeMarketsRequest) returns (stream MarketInfo);
}

// -- market_data/service.proto --------------------------------------------------------

service MarketDataService {
  rpc GetOrderBook(GetOrderBookRequest) returns (GetOrderBookResponse);
  rpc SubscribeOrderBook(SubscribeOrderBookRequest) returns (stream SubscribeOrderBookResponse);
  rpc GetTrades(GetTradesRequest) returns (GetTradesResponse);
  rpc SubscribeTrades(SubscribeTradesRequest) returns (stream SubscribeTradesResponse);
  rpc GetCandles(GetCandlesRequest) returns (GetCandlesResponse);
  rpc SubscribeCandles(SubscribeCandlesRequest) returns (stream SubscribeCandlesResponse);
}

// -- order/service.proto --------------------------------------------------------

service OrderService {
  rpc ListOrders(ListOrdersRequest) returns (ListOrdersResponse);
  rpc GetOrder(GetOrderRequest) returns (GetOrderResponse);
  rpc SubscribeOrders(SubscribeOrdersRequest) returns (stream SubscribeOrdersResponse);
  rpc PreviewOrder(PreviewOrderRequest) returns (PreviewOrderResponse);
}

// -- perp_market/service.proto --------------------------------------------------------

service PerpMarketService {
  rpc ListPerpMarkets(ListPerpMarketsRequest) returns (ListPerpMarketsResponse);
  rpc GetPerpMarket(GetPerpMarketRequest) returns (GetPerpMarketResponse);
  rpc GetMarketStats(GetMarketStatsRequest) returns (GetMarketStatsResponse);
  rpc GetMarketHistoryStats(GetMarketHistoryStatsRequest) returns (GetMarketHistoryStatsResponse);
  rpc GetVolumeBreakdown(GetVolumeBreakdownRequest) returns (GetVolumeBreakdownResponse);
  rpc GetVolumeSeries(GetVolumeSeriesRequest) returns (GetVolumeSeriesResponse);
  rpc SubscribeMarketStats(SubscribeMarketStatsRequest) returns (stream SubscribeMarketStatsResponse);
}

// -- risk/service.proto --------------------------------------------------------

// Public read-only margin-risk views.
service RiskService {
  rpc GetMarketOverview(GetMarketOverviewRequest) returns (GetMarketOverviewResponse);
  rpc SimulateAccountRisk(SimulateAccountRiskRequest) returns (SimulateAccountRiskResponse);
  rpc GetInsuranceFund(GetInsuranceFundRequest) returns (GetInsuranceFundResponse);
  rpc ListAccountsAtRisk(ListAccountsAtRiskRequest) returns (ListAccountsAtRiskResponse);
  rpc SubscribeAccountsAtRisk(SubscribeAccountsAtRiskRequest) returns (stream SubscribeAccountsAtRiskResponse);
  rpc ListLargestAccounts(ListLargestAccountsRequest) returns (ListLargestAccountsResponse);
}

// -- sponsorship/service.proto --------------------------------------------------------

// Pays fees for pre-authorized margin transactions; the sponsor is fee payer only.
// Transactions may invoke only the margin, compute-budget, and secp256r1 programs.
service SponsorshipService {
  rpc GetSponsorshipPubkey(GetSponsorshipPubkeyRequest) returns (GetSponsorshipPubkeyResponse);
  rpc SponsorTransaction(SponsorTransactionRequest) returns (SponsorTransactionResponse);
}

// -- account/messages.proto --------------------------------------------------------

message ListAccountsRequest {
  // Base58 owner or delegate pubkey.
  string authority = 1;
}

message AccountRef {
  TraderId trader_id = 1;
  string owner = 2;
  // True when the requested authority is a delegate rather than the owner.
  bool is_delegate = 3;
  string address = 4;
  // World-readable display label, including when is_name_public is false; empty when unset.
  string name = 5;
  // Whether the label is shown on rankings; defaults to false, not an access-control flag.
  bool is_name_public = 6;
}

message ListAccountsResponse {
  repeated AccountRef accounts = 1;
  DataProvenance provenance = 2;
}

enum AccountRankingMetric {
  ACCOUNT_RANKING_METRIC_UNSPECIFIED = 0;
  // Signed USD NAV change minus external cash flows.
  ACCOUNT_RANKING_METRIC_PNL = 1;
  // Linked hourly Modified Dietz returns, in percent (10 means 10%).
  ACCOUNT_RANKING_METRIC_PNL_PERCENT = 2;
  // Sum over completed-hour buckets in the selected timeframe.
  ACCOUNT_RANKING_METRIC_VOLUME = 3;
  ACCOUNT_RANKING_METRIC_ACCOUNT_VALUE = 4;
  ACCOUNT_RANKING_METRIC_OPEN_INTEREST = 5;
  // Health ratio ascending; accounts without usable prices are excluded.
  ACCOUNT_RANKING_METRIC_CLOSEST_TO_LIQUIDATION = 6;
  // Largest single liquidation, not the sum, over completed-hour buckets.
  ACCOUNT_RANKING_METRIC_LARGEST_LIQUIDATION = 7;
  // Annualized hourly Sharpe, zero risk-free rate; requires 24 returns.
  ACCOUNT_RANKING_METRIC_SHARPE_RATIO = 8;
}

enum AccountRankingTimeframe {
  ACCOUNT_RANKING_TIMEFRAME_UNSPECIFIED = 0;
  ACCOUNT_RANKING_TIMEFRAME_DAY = 1;
  ACCOUNT_RANKING_TIMEFRAME_WEEK = 2;
  ACCOUNT_RANKING_TIMEFRAME_MONTH = 3;
  // All observed history.
  ACCOUNT_RANKING_TIMEFRAME_ALL_TIME = 4;
}

enum AccountRankingSize {
  // No minimum account value.
  ACCOUNT_RANKING_SIZE_UNSPECIFIED = 0;
  ACCOUNT_RANKING_SIZE_USD_100 = 1;
  ACCOUNT_RANKING_SIZE_USD_1000 = 2;
  ACCOUNT_RANKING_SIZE_USD_10000 = 3;
  ACCOUNT_RANKING_SIZE_USD_100000 = 4;
  ACCOUNT_RANKING_SIZE_USD_1000000 = 5;
}

message GetRankedAccountsRequest {
  // All metrics sort descending except CLOSEST_TO_LIQUIDATION.
  AccountRankingMetric metric = 1;
  // Historical windows are rolling 24h, 7d, or 30d completed hours.
  // Current-state rankings ignore this field and leave response windows unset.
  AccountRankingTimeframe timeframe = 2;
  // Minimum unweighted USD economic NAV.
  AccountRankingSize minimum_account_value = 3;
  // Zero uses the default (20); maximum 100. Results are not paginated.
  uint32 limit = 4;
}

message RankedAccount {
  TraderId trader_id = 1;
  // Live identity and statistics may be absent for historical-only accounts.
  string owner = 2;
  string address = 3;
  // Unweighted USD economic NAV.
  Decimal account_value_usd = 4;
  Decimal metric_value = 5;
  uint32 rank = 6;
  // Current statistics are independent of the historical ranking window.
  Decimal open_interest_usd = 7;
  // Gross perp notional / positive economic NAV; absent otherwise.
  Decimal leverage = 8;
  Decimal health_ratio = 9;
  // Number of perp positions with nonzero quantity.
  optional uint32 open_position_count = 10;
  // Net equity from current account health, including collateral weights.
  Decimal account_equity_usd = 11;
  // First observation used for performance; all-time begins with eligible NAV history.
  Timestamp performance_start = 12;
  // Only public labels are returned; empty when unset or private.
  string name = 13;
}

message GetRankedAccountsResponse {
  repeated RankedAccount accounts = 1;
  // Timestamp at which the ranking was built.
  Timestamp as_of = 2;
  Timestamp window_start = 3;
  Timestamp window_end = 4;
  DataProvenance provenance = 5;
  // Always false while processed archive history may have gaps.
  bool history_complete = 6;
}

message GetAccountRequest {
  TraderId trader_id = 1;
}

message GetAccountResponse {
  AccountSnapshot account = 1;
}

message SubscribeAccountRequest {
  TraderId trader_id = 1;
}

// One spot leg. Amounts are token units.
message SpotPosition {
  SpotId spot_id = 1;
  // Idle collateral that earns no interest.
  Decimal raw_deposit = 2;
  Decimal lent = 3;
  Decimal borrowed = 4;
  // Unweighted USD value.
  Decimal value_usd = 5;

  // Raw pool share counts.
  Decimal deposit_shares = 6;
  Decimal borrow_shares = 7;

  // USD collateral after the deposit-weight haircut; zero for a net borrow.
  Decimal weighted_collateral_usd = 8;
  // USD debt; zero for a net deposit.
  Decimal debt_usd = 9;
  Decimal initial_margin_usd = 10;
  Decimal maintenance_margin_usd = 11;

  // USD token price at maintenance margin; unset when no single-asset move can reach it.
  Decimal liquidation_price = 12;
}

// Cross-margin account health in USD.
message AccountHealth {
  Decimal net_equity = 1;
  // Required to borrow or withdraw collateral.
  Decimal initial_margin = 2;
  Decimal maintenance_margin = 3;
  Decimal net_exposure = 4;
  bool meets_initial_margin = 5;
  bool meets_maintenance_margin = 6;

  // net_equity minus initial_margin, floored at zero.
  Decimal free_collateral = 7;
  // maintenance_margin minus net_equity, floored at zero.
  Decimal deficit = 8;
  // net_equity / maintenance_margin; unset when maintenance margin is zero.
  Decimal health_ratio = 9;
  // Requires initial margin and positive equity.
  bool can_open_position = 10;
  // Whether a backstopper may take over the account's legs.
  bool can_be_backstopped = 11;
}

message AccountSnapshot {
  TraderId trader_id = 1;
  string owner = 2;
  repeated SpotPosition positions = 3;
  AccountHealth health = 4;
  uint64 slot = 5;
  Timestamp ts = 6;
  string address = 7;
  DataProvenance provenance = 8;
  repeated PerpPosition perp_positions = 9;
  // False when combined spot/perp health is unavailable.
  bool perp_positions_supported = 10;
}

// Constraint on an action amount.
enum LimitConstraint {
  LIMIT_CONSTRAINT_UNSPECIFIED = 0;
  // The available balance; `deposit` uses uint64 maximum because wallet balance is unknown.
  LIMIT_CONSTRAINT_BALANCE = 1;
  LIMIT_CONSTRAINT_INITIAL_MARGIN = 2;
  LIMIT_CONSTRAINT_POOL_LIQUIDITY = 3;
  LIMIT_CONSTRAINT_UTILISATION_THROTTLE = 4;
  LIMIT_CONSTRAINT_MARKET_CAP = 5;
  LIMIT_CONSTRAINT_FLOW_CAP = 6;
  // For borrow, the redeem flag also applies.
  LIMIT_CONSTRAINT_OPERATION_DISABLED = 7;
  // A new position would exceed the account's spot-position limit.
  LIMIT_CONSTRAINT_POSITION_LIMIT = 8;
}

message ActionLimit {
  // Token units; zero when unavailable.
  Decimal amount = 1;
  LimitConstraint constraint = 2;
}

// Per-market action limits at the response state; they may change before submission.
message MarketActionLimits {
  SpotId spot_id = 1;
  ActionLimit deposit = 2;
  ActionLimit withdraw = 3;
  ActionLimit lend = 4;
  ActionLimit redeem = 5;
  ActionLimit borrow = 6;
  ActionLimit repay = 7;
}

message GetActionLimitsRequest {
  TraderId trader_id = 1;
  // Empty selects every listed market.
  repeated SpotId spot_ids = 2;
}

message GetActionLimitsResponse {
  repeated MarketActionLimits markets = 1;
  uint64 slot = 2;
  Timestamp ts = 3;
  DataProvenance provenance = 4;
}

enum SpotAction {
  SPOT_ACTION_UNSPECIFIED = 0;
  SPOT_ACTION_DEPOSIT = 1;
  // Debits only the idle deposit and never borrows; must leave initial margin met.
  SPOT_ACTION_WITHDRAW = 2;
  SPOT_ACTION_LEND = 3;
  SPOT_ACTION_REDEEM = 4;
  SPOT_ACTION_BORROW = 5;
  SPOT_ACTION_REPAY = 6;
}

// Simulates an action without signing or submitting it.
message SimulateActionRequest {
  TraderId trader_id = 1;
  SpotId spot_id = 2;
  SpotAction action = 3;
  // Token units; excess asset precision is rejected.
  Decimal amount = 4;
  // Base58 owner or registered delegate pubkey used for simulation.
  string authority = 5;
  // Creates the signer's token account; rejected for actions that move no tokens.
  bool create_token_account = 6;
  // Lamports to wrap before a wrapped-SOL deposit; zero disables wrapping.
  uint64 wrap_lamports = 7;
}

// Simulation result and post-state.
message SimulateActionResponse {
  // Empty on success; otherwise the node's transaction error.
  string error = 1;
  repeated string logs = 2;
  // Minimum compute-unit limit for the simulated transaction.
  uint64 compute_units = 3;
  // Simulated post-state; unset on error.
  AccountSnapshot account = 4;
  Timestamp ts = 5;
  // Stable margin-program error name; empty for failures from other programs.
  string program_error = 6;
  DataProvenance provenance = 7;
}

enum ActivityKind {
  ACTIVITY_KIND_UNSPECIFIED = 0;
  ACTIVITY_KIND_DEPOSIT = 1;
  ACTIVITY_KIND_WITHDRAW = 2;
  ACTIVITY_KIND_LEND = 3;
  ACTIVITY_KIND_REDEEM = 4;
  ACTIVITY_KIND_BORROW = 5;
  ACTIVITY_KIND_REPAY = 6;
  // Bad debt written off this account.
  ACTIVITY_KIND_SOCIALIZE_LOSS = 7;
}

message ActivityEntry {
  ActivityKind kind = 1;
  SpotId spot_id = 2;
  uint64 slot = 3;
  // Server observation time, not block time.
  Timestamp ts = 4;
  string signature = 5;

  // Unsigned token units moved; unset when unknown.
  Decimal amount = 6;
  // Estimated USD value at ingestion; unset without a usable price.
  Decimal value_usd = 7;
  // True when this account was the liquidator.
  bool liquidation = 8;
  string event_id = 9;
  DataProvenance provenance = 10;
  bool perp_settlement = 11;
}

message GetActivityRequest {
  TraderId trader_id = 1;
  // Inclusive; unset selects the whole ledger.
  Timestamp start = 2;
  // Exclusive; unset selects now.
  Timestamp end = 3;
  // Zero uses the default (50); maximum 1000.
  uint32 limit = 4;
  // Empty selects every kind; UNSPECIFIED values are ignored.
  repeated ActivityKind kinds = 5;
  string page_token = 6;
}

message GetActivityResponse {
  // Descending by slot, signature, instruction index, then event index;
  // pagination is not a snapshot.
  repeated ActivityEntry activities = 1;
  string next_page_token = 2;
  DataProvenance provenance = 3;
  HistoryCoverage coverage = 4;
}

// The signed bytes are UTF-8, no trailing newline, fields in this order.
// Insert the is_public line only when the field is present, using lowercase true or false.
// Explicit false signs the line; omission leaves it out. The server verifies only that layout.
//
//     flint:rename-account\n
//     expires_at=<expires_at>\n
//     trader_id=<trader_id>\n
//     is_public=<true|false>\n
//     name=<name>
//
message RenameAccountRequest {
  TraderId trader_id = 1;
  // Base58 signing owner or delegate pubkey.
  string authority = 2;
  // Empty clears the label. Trimmed; maximum 32 characters. Sign the trimmed value.
  string name = 3;
  // Unix seconds, at most 120 seconds ahead.
  uint64 expires_at = 4;
  bytes signature = 5;
  // Omission resets leaderboard visibility to false, including for an already-public label.
  optional bool is_public = 6;
}

message RenameAccountResponse {
  string name = 1;
  bool is_public = 2;
}

// One archived account-value sample.
message AccountHistoryPoint {
  // Bucket start; uses its newest sample.
  Timestamp ts = 1;
  // Server observation time, not block time.
  Timestamp observed_at = 2;
  // Highest observed tip; may not increase between points.
  uint64 slot = 3;

  // Unweighted signed account value.
  Decimal nav_usd = 4;

  // nav_usd minus deposits; estimated and unset without complete ledger coverage.
  Decimal pnl_usd = 5;

  Decimal net_exposure = 6;
  Decimal debt_usd = 7;
  // net_equity / maintenance_margin; unset when maintenance margin is zero.
  Decimal health_ratio = 8;
  DataProvenance provenance = 9;
}

message GetAccountHistoryRequest {
  TraderId trader_id = 1;
  // Inclusive; unset selects 24 hours before end.
  Timestamp start = 2;
  // Exclusive; unset selects now.
  Timestamp end = 3;
  // Zero lets the server choose; non-zero is exact or INVALID_ARGUMENT.
  uint32 resolution_seconds = 4;
  bool include_perp_pnl = 5;
}

message GetAccountHistoryResponse {
  // Oldest first; empty buckets are omitted.
  repeated AccountHistoryPoint points = 1;
  // Actual bucket width.
  uint32 resolution_seconds = 2;
  HistoryCoverage coverage = 3;
  PerpPnlHistory perp_pnl = 4;
}

message BatchGetAccountsRequest {
  // Required; at most 100 unique IDs. Results preserve request order.
  repeated TraderId trader_ids = 1;
}

message BatchGetAccountsResponse {
  repeated AccountSnapshot accounts = 1;
  // No local valued snapshot; does not prove on-chain absence.
  repeated TraderId missing_trader_ids = 2;
  DataProvenance provenance = 3;
}

message SubscribeAccountsRequest {
  repeated TraderId trader_ids = 1;
}

// Each message replaces the selected account set; intermediate states may coalesce.
message SubscribeAccountsResponse {
  repeated AccountSnapshot accounts = 1;
  // No local valued snapshot; does not prove on-chain absence.
  repeated TraderId missing_trader_ids = 2;
  DataProvenance provenance = 3;
}

message ListDelegatesRequest {
  TraderId trader_id = 1;
  bool include_inactive = 2;
}

enum DelegateCredentialKind {
  DELEGATE_CREDENTIAL_KIND_UNSPECIFIED = 0;
  DELEGATE_CREDENTIAL_KIND_ED25519 = 1;
  DELEGATE_CREDENTIAL_KIND_PASSKEY = 2;
}

message DelegateInfo {
  string address = 1;
  DelegateCredentialKind credential_kind = 2;
  string authority = 3;
  uint32 scope_bits = 4;
  Timestamp expires_at = 5;
  bool active = 6;
}

message ListDelegatesResponse {
  repeated DelegateInfo delegates = 1;
  DataProvenance provenance = 2;
}

message SubscribeActivityRequest {
  TraderId trader_id = 1;
  repeated ActivityKind kinds = 2;
}

// -- action/messages.proto --------------------------------------------------------

// One trader action. Markets are perp symbols as the trading app shows them
// ("SOL-USDC-PERP"), or a base ("SOL") when only one market trades it. Assets
// are token names ("USDC"); mints are also accepted. Sizes are base units,
// amounts token units, and prices quote prices, all as decimal strings.
message TraderAction {
  oneof action {
    PlaceOrderAction place_order = 1;
    ClosePositionAction close_position = 2;
    CancelOrderAction cancel_order = 3;
    SettlePnlAction settle_pnl = 4;
    AssetAction deposit = 5;
    // Pays only the account owner's wallet.
    AssetAction withdraw = 6;
    TransferAction transfer = 7;
    AssetAction lend = 8;
    AssetAction redeem = 9;
    AssetAction borrow = 10;
    AssetAction repay = 11;
  }
}

// UNSPECIFIED is INVALID_ARGUMENT.
enum ActionOrderSide {
  ACTION_ORDER_SIDE_UNSPECIFIED = 0;
  ACTION_ORDER_SIDE_LONG = 1;
  ACTION_ORDER_SIDE_SHORT = 2;
}

// UNSPECIFIED is INVALID_ARGUMENT.
enum ActionOrderType {
  ACTION_ORDER_TYPE_UNSPECIFIED = 0;
  // Fill or kill within max_slippage_bps of the mark the trader reviews.
  ACTION_ORDER_TYPE_MARKET = 1;
  ACTION_ORDER_TYPE_LIMIT = 2;
}

enum ActionTimeInForce {
  // GTC.
  ACTION_TIME_IN_FORCE_UNSPECIFIED = 0;
  ACTION_TIME_IN_FORCE_GTC = 1;
  ACTION_TIME_IN_FORCE_IOC = 2;
  ACTION_TIME_IN_FORCE_FOK = 3;
}

message PlaceOrderAction {
  string market = 1;
  ActionOrderSide side = 2;
  // A multiple of the market's lot size; never rounded.
  Decimal size = 3;
  ActionOrderType order_type = 4;
  // Limit orders only, and required for them. A multiple of the market's tick.
  Decimal limit_price = 5;
  // Market orders only: 1 to 1000, else INVALID_ARGUMENT. Absent is 500. The
  // link always carries the value, so the app enforces the band checked here.
  optional uint32 max_slippage_bps = 6;
  // Limit orders only; on a market order, anything but UNSPECIFIED is
  // INVALID_ARGUMENT.
  ActionTimeInForce time_in_force = 7;
  bool reduce_only = 8;
  // Limit GTC orders only, else INVALID_ARGUMENT.
  bool post_only = 9;
}

message ClosePositionAction {
  string market = 1;
  // Percent of the position to close at market: 25, 50, 75 or 100, else
  // INVALID_ARGUMENT.
  uint32 fraction = 2;
  // 1 to 1000, as for a market order. Absent is 500, written into the link.
  optional uint32 max_slippage_bps = 3;
}

// Set target: unset is INVALID_ARGUMENT.
message CancelOrderAction {
  string market = 1;
  oneof target {
    // `<perpId>:<buy|sell>:<priceTicks>`, as the trading app names resting
    // orders.
    string order_id = 2;
    // Every resting order on the market. Must be true; false is
    // INVALID_ARGUMENT. Resting orders aren't indexed by the server: whether
    // a single order still rests is checked by the app when the link opens.
    bool all = 3;
  }
}

// Set target: unset is INVALID_ARGUMENT.
message SettlePnlAction {
  oneof target {
    string market = 1;
    // Every market with pending PnL. Must be true; false is INVALID_ARGUMENT.
    bool all = 2;
  }
}

message AssetAction {
  string asset = 1;
  Decimal amount = 2;
}

message TransferAction {
  string asset = 1;
  Decimal amount = 2;
  // Another account with the same owner.
  TraderId to_trader_id = 3;
}

enum ActionPreviewStatus {
  ACTION_PREVIEW_STATUS_UNSPECIFIED = 0;
  ACTION_PREVIEW_STATUS_READY = 1;
  // Can be approved; see warnings.
  ACTION_PREVIEW_STATUS_WARNING = 2;
  // Can't be approved; see blocked_reason. Any BLOCKED row means no link.
  ACTION_PREVIEW_STATUS_BLOCKED = 3;
}

message ActionDetail {
  // For example "Est. fill".
  string label = 1;
  // For example "102.31".
  string value = 2;
}

message ActionPreviewRow {
  // The action's index in the request.
  uint32 index = 1;
  // When it runs: 1 deposits; 2 cancels, settles, lends, repays and borrows;
  // 3 orders and closes; 4 withdrawals, transfers and redeems. Within a step
  // the request's order is kept.
  uint32 step = 2;
  // The sentence the trading app shows for this action.
  string title = 3;
  ActionPreviewStatus status = 4;
  repeated ActionDetail details = 5;
  repeated string warnings = 6;
  string blocked_reason = 7;
}

message PreviewActionsRequest {
  // The account the actions are for. Its owner reviews and signs them.
  TraderId trader_id = 1;
  // 1 to 10 actions.
  repeated TraderAction actions = 2;
  // How long the link stays valid. Absent is 600; 1 to 900, else
  // INVALID_ARGUMENT.
  optional uint32 expires_in_secs = 3;
}

message PreviewActionsResponse {
  // One per action, in the order they run.
  repeated ActionPreviewRow rows = 1;
  // The trading app's review page with every action. Empty when any action is
  // BLOCKED, or when no trading app is deployed for this cluster; warnings say
  // which.
  string link = 2;
  Timestamp expires_at = 3;
  // The slot the checks read.
  uint64 slot = 4;
  // About the batch as a whole: why there is no link, and that actions were
  // checked alone.
  repeated string warnings = 5;
}

// -- common.proto --------------------------------------------------------

// On-chain spot-asset id (u32 on-chain, unlike flint.spot.v1's u64).
message SpotId {
  uint32 id = 1;
}

// On-chain margin-account ID chosen by its creator.
message TraderId {
  uint64 id = 1;
}

// Decimal string (e.g. "155.14"), parsed with rust_decimal-compatible semantics.
message Decimal {
  string value = 1;
}

// Unix microseconds since epoch. Zero means unspecified.
message Timestamp {
  uint64 micros = 1;
}

enum Commitment {
  COMMITMENT_UNSPECIFIED = 0;
  COMMITMENT_PROCESSED = 1;
  COMMITMENT_CONFIRMED = 2;
  COMMITMENT_FINALIZED = 3;
}

// Observation metadata, not a claim that the node is caught up with the chain.
message DataProvenance {
  Commitment commitment = 1;
  Timestamp observed_at = 2;
  uint64 slot = 3;
  optional uint64 revision = 4;
  bool fork_reconciled = 5;
}

// Effective query window [start, end). Complete only when coverage is proven.
message HistoryCoverage {
  Timestamp start = 1;
  Timestamp end = 2;
  bool complete = 3;
  string reason = 4;
}

message SpotMetadata {
  SpotId id = 1;
  string name = 2;
  string mint = 3;
  uint32 decimals = 4;
  // Base58 mint owner program (SPL Token or Token-2022).
  string token_program = 5;
  string icon = 6;
  // On-chain catalog product ID; separate from `id` and unset on older servers.
  optional uint64 product_id = 7;
}

// -- funding/messages.proto --------------------------------------------------------

message FundingRate {
  PerpId perp_id = 1;
  // Fraction per interval_seconds; positive means longs pay shorts.
  Decimal rate = 2;
  Timestamp funding_at = 3;
  Timestamp next_funding_at = 4;
  uint64 interval_seconds = 5;
  DataProvenance provenance = 6;
}

message FundingPayment {
  string payment_id = 1;
  TraderId trader_id = 2;
  PerpId perp_id = 3;
  // Positive is a credit to the account; negative is a debit.
  Decimal amount_usd = 4;
  Decimal position_quantity = 5;
  Decimal funding_rate = 6;
  Timestamp funded_at = 7;
  DataProvenance provenance = 8;
}

message GetFundingRatesRequest {
  repeated PerpId perp_ids = 1;
}

message GetFundingRatesResponse {
  repeated FundingRate rates = 1;
  DataProvenance provenance = 2;
}

message SubscribeFundingRatesRequest {
  repeated PerpId perp_ids = 1;
}

message SubscribeFundingRatesResponse {
  repeated FundingRate rates = 1;
  bool snapshot = 2;
  uint64 sequence = 3;
  DataProvenance provenance = 4;
}

message GetFundingRateHistoryRequest {
  repeated PerpId perp_ids = 1;
  Timestamp start = 2;
  Timestamp end = 3;
  uint32 page_size = 4;
  string page_token = 5;
  // 0 returns every sample. Otherwise returns only each perp's last sample in every
  // epoch-aligned bucket_seconds window of funding_at.
  uint32 bucket_seconds = 6;
}

message GetFundingRateHistoryResponse {
  repeated FundingRate rates = 1;
  string next_page_token = 2;
  HistoryCoverage coverage = 3;
}

message GetFundingPaymentsRequest {
  TraderId trader_id = 1;
  repeated PerpId perp_ids = 2;
  Timestamp start = 3;
  Timestamp end = 4;
  uint32 page_size = 5;
  string page_token = 6;
}

message GetFundingPaymentsResponse {
  repeated FundingPayment payments = 1;
  string next_page_token = 2;
  HistoryCoverage coverage = 3;
}

message SubscribeFundingPaymentsRequest {
  TraderId trader_id = 1;
  repeated PerpId perp_ids = 2;
}

message SubscribeFundingPaymentsResponse {
  repeated FundingPayment payments = 1;
  bool snapshot = 2;
  uint64 sequence = 3;
  DataProvenance provenance = 4;
}

// -- history/messages.proto --------------------------------------------------------

message MarketHistoryPoint {
  // Start of the requested bucket. Empty buckets are omitted.
  Timestamp ts = 1;
  // Newest coherent observation in the bucket.
  Timestamp observed_at = 2;
  uint64 slot = 3;
  Decimal deposit_apr = 4;
  Decimal borrow_apr = 5;
  Decimal utilisation = 6;
  Decimal vault_utilisation = 7;
  Decimal total_deposits = 8;
  Decimal total_borrows = 9;
  Decimal total_isolated_deposit = 10;
  Decimal deposit_share_price = 11;
  Decimal borrow_share_price = 12;
  Decimal oracle_price = 13;
  Decimal protocol_fee = 14;
  DataProvenance provenance = 15;
  Decimal available_liquidity = 16;
}

message GetMarketHistoryRequest {
  SpotId spot_id = 1;
  // Inclusive. Unset / zero-micros selects 24 hours before end.
  Timestamp start = 2;
  // Exclusive. Unset / zero-micros selects now.
  Timestamp end = 3;
  // Zero lets the server choose a bucket width that fits its point limit.
  uint32 resolution_seconds = 4;
}

message GetMarketHistoryResponse {
  repeated MarketHistoryPoint points = 1;
  uint32 resolution_seconds = 2;
  HistoryCoverage coverage = 3;
}

message InterestHistoryPoint {
  SpotId spot_id = 1;
  // Start of the requested bucket.
  Timestamp ts = 2;
  // Token interest earned; unset when the ledger cannot establish it.
  Decimal earned = 3;
  // Token interest paid; unset when the ledger cannot establish it.
  Decimal paid = 4;
  // Why one or both values are absent. Empty only when both are known.
  string unavailable_reason = 5;
  DataProvenance provenance = 6;
}

message GetInterestHistoryRequest {
  TraderId trader_id = 1;
  // Empty selects every observed spot asset.
  repeated SpotId spot_ids = 2;
  // Inclusive. Unset / zero-micros selects 24 hours before end.
  Timestamp start = 3;
  // Exclusive. Unset / zero-micros selects now.
  Timestamp end = 4;
  // Zero lets the server choose a bucket width that fits its point limit.
  uint32 resolution_seconds = 5;
}

message GetInterestHistoryResponse {
  repeated InterestHistoryPoint points = 1;
  uint32 resolution_seconds = 2;
  HistoryCoverage coverage = 3;
}

// -- market/messages.proto --------------------------------------------------------

// Operations currently accepted by the market.
message MarketFlags {
  bool deposit_enabled = 1;
  bool withdraw_enabled = 2;
  bool lend_enabled = 3;
  bool borrow_enabled = 4;
  bool redeem_enabled = 5;
}

// Per-market net-flow limiter.
message FlowCap {
  bool deposit_capped = 1;
  bool withdraw_capped = 2;
  // Net-inflow ceiling in token units; unset when uncapped.
  Decimal max_deposit = 3;
  // Net-outflow ceiling, token units. Unset when uncapped.
  Decimal max_withdraw = 4;
  // Current token-unit headroom; unset when uncapped.
  Decimal deposit_headroom = 5;
  Decimal withdraw_headroom = 6;
}

// Live piecewise-linear borrow-rate curve in utilisation.
message InterestRateCurve {
  // Curve kink; bounded on-chain to [0.05, 0.95].
  Decimal target_utilisation = 1;
  Decimal apr_at_zero = 2;
  Decimal apr_at_target = 3;
  Decimal apr_at_full = 4;
}

enum OracleProvider {
  ORACLE_PROVIDER_UNSPECIFIED = 0;
  ORACLE_PROVIDER_DEV = 1;
  ORACLE_PROVIDER_PYTH = 2;
  ORACLE_PROVIDER_CHAINLINK = 3;
}

// One oracle source slot.
message OracleSource {
  OracleProvider provider = 1;
  // Base58 source account.
  string feed = 2;
}

// Market oracle source and cache freshness.
message OracleInfo {
  reserved 1, 2;
  reserved "provider", "feed";

  Timestamp last_synced_at = 3;
  // On-chain reads after this time are rejected.
  Timestamp valid_until = 4;

  // Registry oracle ID; markets with the same ID share a price.
  uint32 oracle_id = 5;
  // Configured slots in order; empty until indexed.
  repeated OracleSource sources = 6;
}

// Lending-market state. Token amounts are token units.
message MarketInfo {
  SpotMetadata asset = 1;

  // Deposit-value haircut for collateral.
  Decimal deposit_weight = 2;
  Decimal borrow_imf = 3;
  Decimal borrow_mmf = 4;

  // USD per token unit.
  Decimal oracle_price = 5;

  // Token units per pool share.
  Decimal deposit_share_price = 6;
  Decimal borrow_share_price = 7;

  // Total supplied token units, including isolated deposits.
  Decimal total_deposits = 8;
  Decimal total_borrows = 9;
  // total_borrows / total_deposits; distinct from vault_utilisation.
  Decimal utilisation = 10;

  // Nominal annual rates at current utilisation.
  Decimal deposit_apr = 12;
  Decimal borrow_apr = 13;

  uint64 slot = 14;
  Timestamp ts = 15;

  // Collateral bonus per unit of debt repaid by a liquidator.
  Decimal liquidation_bonus = 16;

  // Protocol share of interest, already reflected in deposit_apr.
  Decimal lending_rate_fee = 17;
  // Unpaid protocol interest in token units.
  Decimal protocol_fee = 18;

  // Vault utilisation at or above which the chain rejects a new borrow.
  Decimal throttle_borrow_utilisation = 19;
  // Vault utilisation at which the chain rejects redeeming lent deposits.
  Decimal throttle_redeem_utilisation = 20;
  // Total-asset ceiling in token units; unset when uncapped.
  Decimal deposit_limit = 21;
  // Pool-borrow ceiling in token units; unset when uncapped.
  Decimal borrow_limit = 22;
  MarketFlags flags = 23;
  FlowCap flow_cap = 24;

  // Idle, unlent deposits in token units.
  Decimal total_isolated_deposit = 25;
  // total_borrows / lent pool; capped at 1.
  Decimal vault_utilisation = 26;
  // Current borrow/redeem headroom in token units.
  Decimal available_liquidity = 27;
  // Raw pool-share totals.
  Decimal total_deposit_shares = 28;
  Decimal total_borrow_shares = 29;
  // Interest-accrual time; share prices and APRs are as of this time.
  Timestamp last_accrual_at = 30;
  InterestRateCurve rate_curve = 31;

  string address = 32;
  string vault = 33;
  OracleInfo oracle = 34;
  DataProvenance provenance = 35;
}

message ListMarketsRequest {}

message ListMarketsResponse {
  repeated MarketInfo markets = 1;
  DataProvenance provenance = 2;
}

message GetMarketRequest {
  SpotId spot_id = 1;
}

message GetMarketResponse {
  MarketInfo market = 1;
}

message SubscribeMarketsRequest {
  // Empty selects every listed market.
  repeated SpotId spot_ids = 1;
}

// -- market_data/messages.proto --------------------------------------------------------

enum CandleInterval {
  CANDLE_INTERVAL_UNSPECIFIED = 0;
  CANDLE_INTERVAL_1_MINUTE = 1;
  CANDLE_INTERVAL_5_MINUTES = 2;
  CANDLE_INTERVAL_15_MINUTES = 3;
  CANDLE_INTERVAL_1_HOUR = 4;
  CANDLE_INTERVAL_4_HOURS = 5;
  CANDLE_INTERVAL_1_DAY = 6;
}

enum CandlePriceSource {
  CANDLE_PRICE_SOURCE_UNSPECIFIED = 0;
  CANDLE_PRICE_SOURCE_TRADES = 1;
  CANDLE_PRICE_SOURCE_MARK = 2;
  CANDLE_PRICE_SOURCE_INDEX = 3;
}

message OrderBookLevel {
  Decimal price = 1;
  Decimal quantity = 2;
  uint64 order_count = 3;
}

message OrderBookSnapshot {
  PerpId perp_id = 1;
  repeated OrderBookLevel bids = 2;
  repeated OrderBookLevel asks = 3;
  uint64 sequence = 4;
  DataProvenance provenance = 5;
}

message PublicTrade {
  string trade_id = 1;
  PerpId perp_id = 2;
  TradeSide aggressor_side = 3;
  Decimal price = 4;
  Decimal quantity = 5;
  Timestamp executed_at = 6;
  DataProvenance provenance = 7;
}

message Candle {
  PerpId perp_id = 1;
  CandleInterval interval = 2;
  CandlePriceSource price_source = 3;
  Timestamp opened_at = 4;
  Timestamp closed_at = 5;
  Decimal open = 6;
  Decimal high = 7;
  Decimal low = 8;
  Decimal close = 9;
  Decimal volume = 10;
  uint64 trade_count = 11;
  DataProvenance provenance = 12;
}

message GetOrderBookRequest {
  PerpId perp_id = 1;
  uint32 depth = 2;
}

message GetOrderBookResponse {
  OrderBookSnapshot order_book = 1;
}

message SubscribeOrderBookRequest {
  PerpId perp_id = 1;
  uint32 depth = 2;
}

message SubscribeOrderBookResponse {
  // Snapshot replaces the book. Deltas replace sizes by price; zero deletes.
  OrderBookSnapshot order_book = 1;
  bool snapshot = 2;
  uint64 sequence = 3;
  uint64 previous_sequence = 4;
}

message GetTradesRequest {
  repeated PerpId perp_ids = 1;
  Timestamp start = 2;
  Timestamp end = 3;
  uint32 page_size = 4;
  string page_token = 5;
}

message GetTradesResponse {
  repeated PublicTrade trades = 1;
  string next_page_token = 2;
  HistoryCoverage coverage = 3;
}

message SubscribeTradesRequest {
  repeated PerpId perp_ids = 1;
}

message SubscribeTradesResponse {
  repeated PublicTrade trades = 1;
  bool snapshot = 2;
  uint64 sequence = 3;
  DataProvenance provenance = 4;
}

message GetCandlesRequest {
  PerpId perp_id = 1;
  CandleInterval interval = 2;
  CandlePriceSource price_source = 3;
  Timestamp start = 4;
  Timestamp end = 5;
  uint32 page_size = 6;
  string page_token = 7;
}

message GetCandlesResponse {
  repeated Candle candles = 1;
  string next_page_token = 2;
  HistoryCoverage coverage = 3;
}

message SubscribeCandlesRequest {
  PerpId perp_id = 1;
  CandleInterval interval = 2;
  CandlePriceSource price_source = 3;
}

message SubscribeCandlesResponse {
  Candle candle = 1;
  bool snapshot = 2;
  uint64 sequence = 3;
  DataProvenance provenance = 4;
}

// -- order/messages.proto --------------------------------------------------------

enum OrderType {
  ORDER_TYPE_UNSPECIFIED = 0;
  ORDER_TYPE_LIMIT = 1;
  ORDER_TYPE_MARKET = 2;
  ORDER_TYPE_STOP_MARKET = 3;
  ORDER_TYPE_STOP_LIMIT = 4;
  ORDER_TYPE_TAKE_PROFIT_MARKET = 5;
  ORDER_TYPE_TAKE_PROFIT_LIMIT = 6;
}

enum TimeInForce {
  TIME_IN_FORCE_UNSPECIFIED = 0;
  TIME_IN_FORCE_GOOD_TIL_CANCELLED = 1;
  TIME_IN_FORCE_IMMEDIATE_OR_CANCEL = 2;
  TIME_IN_FORCE_FILL_OR_KILL = 3;
  TIME_IN_FORCE_POST_ONLY = 4;
}

enum OrderStatus {
  ORDER_STATUS_UNSPECIFIED = 0;
  ORDER_STATUS_OPEN = 1;
  ORDER_STATUS_PARTIALLY_FILLED = 2;
  ORDER_STATUS_FILLED = 3;
  ORDER_STATUS_CANCELLED = 4;
  ORDER_STATUS_REJECTED = 5;
  ORDER_STATUS_EXPIRED = 6;
  ORDER_STATUS_TRIGGERED = 7;
}

message Order {
  string order_id = 1;
  string client_order_id = 2;
  TraderId trader_id = 3;
  PerpId perp_id = 4;
  TradeSide side = 5;
  OrderType order_type = 6;
  TimeInForce time_in_force = 7;
  OrderStatus status = 8;
  Decimal quantity = 9;
  Decimal filled_quantity = 10;
  Decimal remaining_quantity = 11;
  Decimal limit_price = 12;
  Decimal trigger_price = 13;
  bool reduce_only = 14;
  bool post_only = 15;
  Timestamp created_at = 16;
  Timestamp updated_at = 17;
  Timestamp expires_at = 18;
  string rejection_reason = 19;
  DataProvenance provenance = 20;
}

message PreviewOrder {
  PerpId perp_id = 1;
  TradeSide side = 2;
  Decimal requested_quantity = 3;
  Decimal executable_quantity = 4;
  Decimal average_price = 5;
  Decimal worst_price = 6;
  Decimal price_impact = 7;
  Decimal estimated_fee_usd = 8;
  Decimal resulting_position_quantity = 9;
  Decimal resulting_initial_margin_usd = 10;
  Decimal resulting_maintenance_margin_usd = 11;
  DataProvenance provenance = 12;
}

message ListOrdersRequest {
  TraderId trader_id = 1;
  repeated PerpId perp_ids = 2;
  repeated OrderStatus statuses = 3;
  Timestamp start = 4;
  Timestamp end = 5;
  uint32 page_size = 6;
  string page_token = 7;
}

message ListOrdersResponse {
  repeated Order orders = 1;
  string next_page_token = 2;
  HistoryCoverage coverage = 3;
}

message GetOrderRequest {
  TraderId trader_id = 1;
  oneof key {
    string order_id = 2;
    string client_order_id = 3;
  }
}

message GetOrderResponse {
  Order order = 1;
}

message SubscribeOrdersRequest {
  TraderId trader_id = 1;
  repeated PerpId perp_ids = 2;
}

message SubscribeOrdersResponse {
  repeated Order orders = 1;
  bool snapshot = 2;
  uint64 sequence = 3;
  DataProvenance provenance = 4;
}

message PreviewOrderRequest {
  TraderId trader_id = 1;
  PerpId perp_id = 2;
  TradeSide side = 3;
  Decimal quantity = 4;
  OrderType order_type = 5;
  Decimal limit_price = 6;
  bool reduce_only = 7;
}

message PreviewOrderResponse {
  PreviewOrder preview = 1;
}

// -- perp/messages.proto --------------------------------------------------------

message PerpId {
  uint32 id = 1;
}

enum TradeSide {
  TRADE_SIDE_UNSPECIFIED = 0;
  TRADE_SIDE_BUY = 1;
  TRADE_SIDE_SELL = 2;
}

enum PerpPositionStatus {
  PERP_POSITION_STATUS_UNSPECIFIED = 0;
  PERP_POSITION_STATUS_OPEN = 1;
  PERP_POSITION_STATUS_CLOSED = 2;
  PERP_POSITION_STATUS_LIQUIDATED = 3;
}

message PerpPosition {
  PerpId perp_id = 1;
  // Signed base units: positive long, negative short.
  Decimal quantity = 2;
  Decimal entry_price = 3;
  Decimal mark_price = 4;
  Decimal notional_usd = 5;
  Decimal unrealized_pnl_usd = 6;
  Decimal realized_pnl_usd = 7;
  // Positive is an estimated credit to the account, negative a debit.
  Decimal accrued_funding_usd = 8;
  Decimal initial_margin_usd = 9;
  Decimal maintenance_margin_usd = 10;
  Decimal liquidation_price = 11;
  PerpPositionStatus status = 12;
  Timestamp opened_at = 13;
  Timestamp updated_at = 14;
  DataProvenance provenance = 15;
}

message PositionHistoryEntry {
  string position_id = 1;
  PerpPosition position = 2;
  Timestamp closed_at = 3;
  Decimal total_fees_usd = 4;
  Decimal total_funding_usd = 5;
}

message Fill {
  string fill_id = 1;
  string order_id = 2;
  string client_order_id = 3;
  TraderId trader_id = 4;
  PerpId perp_id = 5;
  TradeSide side = 6;
  Decimal price = 7;
  Decimal quantity = 8;
  Decimal fee_usd = 9;
  Decimal rebate_usd = 10;
  bool maker = 11;
  Timestamp executed_at = 12;
  DataProvenance provenance = 13;
  Decimal realized_pnl_usd = 14;
}

message TradingLimits {
  PerpId perp_id = 1;
  Decimal max_buy_quantity = 2;
  Decimal max_sell_quantity = 3;
  Decimal reduce_only_buy_quantity = 4;
  Decimal reduce_only_sell_quantity = 5;
  Decimal available_margin_usd = 6;
  DataProvenance provenance = 7;
}

message TradingFees {
  PerpId perp_id = 1;
  Decimal maker_fee_rate = 2;
  Decimal taker_fee_rate = 3;
  Decimal maker_rebate_rate = 4;
  string tier = 5;
  Timestamp effective_at = 6;
  DataProvenance provenance = 7;
}

message GetPositionHistoryRequest {
  TraderId trader_id = 1;
  repeated PerpId perp_ids = 2;
  Timestamp start = 3;
  Timestamp end = 4;
  uint32 page_size = 5;
  string page_token = 6;
}

message GetPositionHistoryResponse {
  repeated PositionHistoryEntry positions = 1;
  string next_page_token = 2;
  HistoryCoverage coverage = 3;
}

message GetFillsRequest {
  TraderId trader_id = 1;
  repeated PerpId perp_ids = 2;
  Timestamp start = 3;
  Timestamp end = 4;
  uint32 page_size = 5;
  string page_token = 6;
}

message GetFillsResponse {
  repeated Fill fills = 1;
  string next_page_token = 2;
  HistoryCoverage coverage = 3;
}

message SubscribeFillsRequest {
  TraderId trader_id = 1;
  repeated PerpId perp_ids = 2;
}

message GetTradingLimitsRequest {
  TraderId trader_id = 1;
  repeated PerpId perp_ids = 2;
}

message GetTradingLimitsResponse {
  repeated TradingLimits limits = 1;
  DataProvenance provenance = 2;
}

message GetTradingFeesRequest {
  TraderId trader_id = 1;
  repeated PerpId perp_ids = 2;
}

message GetTradingFeesResponse {
  repeated TradingFees fees = 1;
  DataProvenance provenance = 2;
}

message PerpPnlHistoryPoint {
  Timestamp ts = 1;
  Decimal total_pnl_usd = 2;
  Decimal realized_trading_pnl_usd = 3;
  // - fees; + rebates/rewards.
  Decimal fees_usd = 4;
  // + credit; - debit.
  Decimal funding_usd = 5;
}

message PerpPnlHistory {
  repeated PerpPnlHistoryPoint points = 1;
}

// -- perp_market/messages.proto --------------------------------------------------------

enum PerpMarketStatus {
  PERP_MARKET_STATUS_UNSPECIFIED = 0;
  PERP_MARKET_STATUS_ACTIVE = 1;
  PERP_MARKET_STATUS_PAUSED = 2;
  PERP_MARKET_STATUS_REDUCE_ONLY = 3;
  PERP_MARKET_STATUS_SETTLING = 4;
  PERP_MARKET_STATUS_CLOSED = 5;
}

message PerpMarket {
  PerpId perp_id = 1;
  string symbol = 2;
  SpotMetadata base_asset = 3;
  SpotMetadata quote_asset = 4;
  SpotMetadata settlement_asset = 5;
  Decimal tick_size = 6;
  Decimal lot_size = 7;
  Decimal min_order_quantity = 8;
  Decimal max_order_quantity = 9;
  Decimal max_position_notional_usd = 10;
  Decimal initial_margin_rate = 11;
  Decimal maintenance_margin_rate = 12;
  PerpMarketStatus status = 13;
  DataProvenance provenance = 14;
  repeated MarginTier margin_tiers = 15;
  Decimal max_open_interest = 16;
}

message MarginTier {
  Decimal min_position_notional_usd = 1;
  Decimal initial_margin_rate = 2;
  Decimal maintenance_margin_rate = 3;
}

message MarketStats {
  PerpId perp_id = 1;
  Decimal mark_price = 2;
  Decimal index_price = 3;
  Decimal oracle_price = 4;
  Decimal last_trade_price = 5;
  Decimal best_bid_price = 6;
  Decimal best_ask_price = 7;
  Decimal volume_24h = 8;
  // Fractional price change over the trailing 24 hours.
  Decimal price_change_24h = 9;
  // Base units, counted once per matched long/short pair.
  Decimal open_interest = 10;
  Decimal funding_rate = 11;
  Timestamp next_funding_at = 12;
  DataProvenance provenance = 13;
  uint64 funding_interval_seconds = 14;
  // Base quantity available at the best quoted price.
  Decimal best_bid_quantity = 15;
  Decimal best_ask_quantity = 16;
}

message ListPerpMarketsRequest {
  repeated PerpId perp_ids = 1;
  repeated PerpMarketStatus statuses = 2;
  uint32 page_size = 3;
  string page_token = 4;
}

message ListPerpMarketsResponse {
  repeated PerpMarket markets = 1;
  string next_page_token = 2;
  DataProvenance provenance = 3;
}

message GetPerpMarketRequest {
  PerpId perp_id = 1;
}

message GetPerpMarketResponse {
  PerpMarket market = 1;
}

message GetMarketStatsRequest {
  repeated PerpId perp_ids = 1;
}

message GetMarketStatsResponse {
  repeated MarketStats stats = 1;
  DataProvenance provenance = 2;
}

message GetMarketHistoryStatsRequest {
  // Empty selects every perpetual market; servers accept at most 100 IDs.
  repeated PerpId perp_ids = 1;
}

message PerpMarketHistoryStats {
  PerpId perp_id = 1;
  // Trailing 24-hour USD volume; absent if unknown or unpriced.
  Decimal volume_24h_usd = 2;
  // Fractional price change: current / reference - 1.
  Decimal price_change_24h_fraction = 3;
  // Candle end for the exact minute [minute(as_of) - 24h - 1m, minute(as_of) - 24h).
  Timestamp price_change_reference_at = 4;
  // Candle end for the latest traded minute in [minute(as_of) - 4m, as_of), capped at as_of.
  Timestamp price_change_as_of = 5;
}

message GetMarketHistoryStatsResponse {
  repeated PerpMarketHistoryStats stats = 1;
  Timestamp as_of = 2;
  HistoryCoverage coverage = 3;
}

message SubscribeMarketStatsRequest {
  repeated PerpId perp_ids = 1;
}

message SubscribeMarketStatsResponse {
  repeated MarketStats stats = 1;
  bool snapshot = 2;
  uint64 sequence = 3;
  DataProvenance provenance = 4;
}

message VolumeWindow {
  // USD notional counted once per trade; absent if any fills cannot be valued.
  Decimal volume_usd = 1;
  uint64 fill_count = 2;
}

message PerpVolume {
  PerpId perp_id = 1;
  VolumeWindow one_hour = 2;
  VolumeWindow twenty_four_hour = 3;
  VolumeWindow thirty_day = 4;
  Decimal last_price = 5;
}

message GetVolumeBreakdownRequest {
  // Empty selects every perpetual market.
  repeated PerpId perp_ids = 1;
}

message GetVolumeBreakdownResponse {
  repeated PerpVolume perps = 1;
  HistoryCoverage coverage = 2;
}

enum StatsWindow {
  STATS_WINDOW_UNSPECIFIED = 0;
  STATS_WINDOW_1H = 1;
  STATS_WINDOW_24H = 2;
  STATS_WINDOW_30D = 3;
}

message GetVolumeSeriesRequest {
  // Unset aggregates every perpetual market.
  PerpId perp_id = 1;
  StatsWindow window = 2;
}

message VolumeBucket {
  Timestamp ts = 1;
  // Absent if any fills in the bucket cannot be valued in USD.
  Decimal volume_usd = 2;
  uint64 fill_count = 3;
}

message GetVolumeSeriesResponse {
  // Ordered oldest first, including empty buckets.
  repeated VolumeBucket buckets = 1;
  uint32 bucket_seconds = 2;
  HistoryCoverage coverage = 3;
}

// -- risk/messages.proto --------------------------------------------------------

message GetMarketOverviewRequest {
  // Simultaneous signed fractional non-stable oracle changes in [-1, 0]; empty uses defaults.
  repeated Decimal non_stable_price_changes = 1;
}

message ProtocolRiskOverview {
  uint64 account_count = 1;
  uint64 depositor_count = 2;
  uint64 borrower_count = 3;
  // Number of non-zero borrow positions.
  uint64 loan_count = 4;
  uint64 accounts_at_risk = 5;
  uint64 liquidatable_accounts = 6;

  Decimal total_deposited_usd = 7;
  Decimal total_borrowed_usd = 8;
  Decimal available_liquidity_usd = 9;
  Decimal net_deposits_usd = 10;
  Decimal protocol_fees_usd = 11;

  Decimal at_risk_health_ratio = 12;
}

message MarketRiskOverview {
  MarketInfo market = 1;

  // Held fixed in liquidation-shock scenarios.
  bool is_stable = 2;

  Decimal total_deposited_usd = 3;
  Decimal total_borrowed_usd = 4;
  Decimal available_liquidity_usd = 5;
  Decimal net_deposits_usd = 6;

  // USD market caps; unset when uncapped.
  Decimal deposit_limit_usd = 7;
  Decimal borrow_limit_usd = 8;
  // Remaining cap headroom, floored at zero; unset when uncapped.
  Decimal deposit_limit_headroom_usd = 9;
  Decimal borrow_limit_headroom_usd = 10;

  // Debt-value factors: 1 + borrow_imf and 1 + borrow_mmf.
  Decimal initial_borrow_weight = 11;
  Decimal maintenance_borrow_weight = 12;

  // Cap usage; may exceed 1 after a cap reduction and is unset when uncapped.
  Decimal deposit_limit_utilisation = 13;
  Decimal borrow_limit_utilisation = 14;
}

message LiquidationShockRow {
  Decimal non_stable_price_change = 1;
  uint64 liquidatable_account_count = 2;
  // Unset when unavailable; liquidation_usd and bad_debt_usd are then unset too.
  optional uint64 bad_debt_account_count = 3;

  // Debt value a liquidation would transfer, including its buffer.
  Decimal liquidation_usd = 4;
  Decimal bad_debt_usd = 5;
  Decimal deficit_usd = 6;
}

message GetMarketOverviewResponse {
  ProtocolRiskOverview overview = 1;
  repeated MarketRiskOverview markets = 2;
  // Request order, or ascending price fall when using defaults.
  repeated LiquidationShockRow liquidation_shocks = 3;

  uint64 slot = 4;
  Timestamp ts = 5;
  DataProvenance provenance = 6;
}

message AssetPriceShock {
  SpotId spot_id = 1;
  // Signed fractional price change in [-1, 10]; at most 20 unique spot IDs.
  Decimal price_change = 2;
}

message SimulateAccountRiskRequest {
  TraderId trader_id = 1;
  // Applied simultaneously by oracle; conflicting oracle changes are rejected.
  repeated AssetPriceShock price_shocks = 2;
}

message SimulateAccountRiskResponse {
  AccountSnapshot current = 1;
  AccountSnapshot stressed = 2;
  DataProvenance provenance = 3;
}

message GetInsuranceFundRequest {}

message InsuranceFundBalance {
  SpotMetadata asset = 1;
  string vault = 2;
  Decimal balance = 3;
}

message InsuranceFund {
  string address = 1;
  // Maximum fraction one socialize-loss instruction may draw; 1 permits all.
  Decimal borrow_lend_socialize_loss_max_draw = 2;
  repeated InsuranceFundBalance balances = 3;
  // False when the fund or a listed market vault was not observed.
  bool complete = 4;
  string unavailable_reason = 5;
}

message GetInsuranceFundResponse {
  InsuranceFund fund = 1;
  DataProvenance provenance = 2;
}

message ListAccountsAtRiskRequest {
  // Gross unweighted deposits in USD; unset bounds are open.
  Decimal min_account_size_usd = 1;
  Decimal max_account_size_usd = 2;

  // Maximum health ratio; unset uses the configured threshold. Accounts without one are excluded.
  Decimal max_health_ratio = 3;
  // True restricts results to liquidatable accounts.
  optional bool liquidatable_only = 4;

  // Zero uses the server default.
  uint32 page_size = 5;
  // Opaque token anchoring pagination to one risk snapshot.
  string page_token = 6;
}

message AccountAtRisk {
  AccountSnapshot account = 1;
  Decimal total_deposited_usd = 2;
  Decimal total_borrowed_usd = 3;
  Decimal net_assets_usd = 4;
  // net_equity - maintenance_margin; negative means liquidatable.
  Decimal maintenance_buffer_usd = 5;
  // Below 1 means liquidatable.
  Decimal health_ratio = 6;
}

message ListAccountsAtRiskResponse {
  // Ordered by health ratio ascending, account size descending, then trader ID ascending.
  repeated AccountAtRisk accounts = 1;
  // Total matching accounts across all pages.
  uint64 total_count = 2;
  string next_page_token = 3;

  // Effective health-ratio cutoff.
  Decimal max_health_ratio = 4;
  uint64 slot = 5;
  Timestamp ts = 6;
  DataProvenance provenance = 7;
}

message SubscribeAccountsAtRiskRequest {}

enum MonitoredAccountStatus {
  MONITORED_ACCOUNT_STATUS_UNSPECIFIED = 0;
  MONITORED_ACCOUNT_STATUS_WATCHING = 1;
  MONITORED_ACCOUNT_STATUS_LIQUIDATABLE = 2;
  // Account is the last known snapshot and may be stale.
  MONITORED_ACCOUNT_STATUS_UNAVAILABLE = 3;
}

message MonitoredAccountAtRisk {
  AccountSnapshot account = 1;
  MonitoredAccountStatus status = 2;
}

message SubscribeAccountsAtRiskResponse {
  // Upserts ordered by trader ID; complete replacement when snapshot is true.
  repeated MonitoredAccountAtRisk upserts = 1;
  // IDs to remove; empty for snapshots.
  repeated TraderId removed_trader_ids = 2;
  bool snapshot = 3;
  // Admission and release thresholds; their gap provides hysteresis.
  Decimal entry_health_ratio = 4;
  Decimal exit_health_ratio = 5;
  uint64 slot = 6;
  Timestamp ts = 7;
  // Monotonically non-decreasing valuation revision.
  uint64 revision = 8;
  DataProvenance provenance = 9;
}

message ListLargestAccountsRequest {
  // Zero uses the server default.
  uint32 page_size = 1;
  // Opaque token anchoring pagination to one risk snapshot.
  string page_token = 2;
}

message ListLargestAccountsResponse {
  // Ordered by gross supplied assets descending, then trader ID ascending.
  // Accounts without maintenance margin remain included with no health ratio.
  repeated AccountAtRisk accounts = 1;
  // Total accounts across all pages.
  uint64 total_count = 2;
  string next_page_token = 3;

  uint64 slot = 4;
  Timestamp ts = 5;
  DataProvenance provenance = 6;
}

// -- sponsorship/messages.proto --------------------------------------------------------

message GetSponsorshipPubkeyRequest {}

message GetSponsorshipPubkeyResponse {
  // Base58 fee payer; leave its signature slot empty for the server.
  string pubkey = 1;
}

message SponsorTransactionRequest {
  // Wire-format VersionedTransaction with only the empty sponsor signature.
  bytes transaction = 1;
}

message SponsorTransactionResponse {
  // Base58 confirmed transaction signature.
  string signature = 1;
}
