Skip to content

Proto schema

The canonical schema for sweetspot.api.v1. Synced from the sweetspot-protos submodule; regenerate SDKs and docs after bumping that submodule.

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

syntax = "proto3";

package sweetspot.api.v1;

// -- Services --------------------------------------------------------

// -- AuthService (auth/service.proto) --------------------------------------------------------

// Issues session tokens. Keypair-auth tokens from Authenticate last 6 hours;
// passwordless organization tokens from VerifyLoginCode last 7 days.
// All RPCs are unauthenticated.
service AuthService {
  // Get a nonce to sign. Idempotent per pubkey within the nonce TTL;
  // calling twice returns distinct nonces but only the most recent is
  // accepted by Authenticate.
  rpc Challenge(ChallengeRequest) returns (ChallengeResponse);

  // Exchange a signed nonce for a 6-hour keypair session token. UNAUTHENTICATED if the
  // pubkey has no outstanding nonce, the nonce has expired, the signature
  // is invalid, or the pubkey is not registered as a quoting authority.
  rpc Authenticate(AuthenticateRequest) returns (AuthenticateResponse);

  // Invalidate a token before its natural expiration.
  rpc Revoke(RevokeRequest) returns (RevokeResponse);

  // Send a 6-digit passwordless login code to an organization user's email.
  // Requests are limited to one per email per minute.
  rpc RequestLoginCode(RequestLoginCodeRequest) returns (RequestLoginCodeResponse);

  // Exchange a passwordless login code for a 7-day bearer session token.
  rpc VerifyLoginCode(VerifyLoginCodeRequest) returns (VerifyLoginCodeResponse);
}

// -- MarketDataService (market_data/service.proto) --------------------------------------------------------

// Public market data.
service MarketDataService {
  // Subscribe to a multiplexed stream of book events (L1/L2/L3) and
  // StatusEvents. The pair set is fixed at stream start; to change
  // subscriptions open a new stream.
  rpc Subscribe(SubscribeRequest) returns (stream MarketDataEvent);

  // Subscribe to fills on a separate stream. Optional per-pair filter;
  // empty = fills from every pair.
  rpc SubscribeFills(SubscribeFillsRequest) returns (stream FillEvent);

  // One-shot book snapshot. Convenient for cold-start reconciliation
  // without opening a streaming RPC.
  rpc GetBook(GetBookRequest) returns (GetBookResponse);

  // Return the catalog of discovered pairs + their spot metadata.
  rpc ListPairs(ListPairsRequest) returns (ListPairsResponse);

  // Stream a consolidated snapshot of every registered market 
  rpc SubscribeMarketSnapshots(SubscribeMarketSnapshotsRequest)
      returns (stream MarketsSnapshotEvent);
}

// -- MakerService (maker/service.proto) --------------------------------------------------------

// Per-maker queries + streaming. Every RPC requires organization-oriented
// auth: either a session token via `authorization: Bearer <token>` metadata
// whose maker_id exists in the organizations table (see AuthService), or an
// `x-api-key` organization API key. The resolved maker_id scopes all responses.
service MakerService {
  // Stream balance updates as the on-chain accounts change.
  rpc SubscribeBalance(SubscribeBalanceRequest) returns (stream MakerBalanceEvent);

  // One-shot snapshot of current balances - useful on cold start before
  // opening the stream.
  rpc GetBalance(GetBalanceRequest) returns (GetBalanceResponse);

  // Stream a consolidated cross-market snapshot enriched with the
  // authenticated maker's own best bid / ask + depth level per pair.
  rpc SubscribeMarketSnapshots(SubscribeMakerMarketSnapshotsRequest)
      returns (stream MakerMarketsSnapshotEvent);

  // 1h / 24h / 30d volume + fill count broken down by pair, scoped to
  // the authenticated maker.
  rpc GetVolumeBreakdown(GetMakerVolumeBreakdownRequest)
      returns (GetMakerVolumeBreakdownResponse);

  // Time-bucketed volume series scoped to the authenticated maker.
  rpc GetVolumeSeries(GetMakerVolumeSeriesRequest)
      returns (GetMakerVolumeSeriesResponse);

  // Time-bucketed historical NAV (USD net asset value) scoped to the
  // authenticated maker.
  rpc GetNavHistory(GetNavHistoryRequest)
      returns (GetNavHistoryResponse);

  // Stream every fill where the authenticated maker provided the
  // liquidity. The taker (counterparty) is the transaction signer; this
  // stream does not include fills where the maker was the taker.
  rpc SubscribeFills(SubscribeMakerFillsRequest) returns (stream MakerFillEvent);

  // Rolling 1s / 1m / 5m activity rates scoped to the authenticated maker:
  // fill rate, TX submission outcomes, plus the venue-wide oracle update
  // rate for cross-reference.
  rpc GetStats(GetMakerStatsRequest) returns (GetMakerStatsResponse);
}

// -- StatsService (stats/service.proto) --------------------------------------------------------

// Public (unauthenticated) aggregate stats about the exchange.
service StatsService {
  rpc GetSummary(GetSummaryRequest) returns (GetSummaryResponse);

  // 1h / 24h / 30d volume + fill count broken down by pair.
  rpc GetVolumeBreakdown(GetVolumeBreakdownRequest) returns (GetVolumeBreakdownResponse);

  rpc GetVolumeSeries(GetVolumeSeriesRequest) returns (GetVolumeSeriesResponse);

  rpc GetAssetTvls(GetAssetTvlsRequest) returns (GetAssetTvlsResponse);
}

// -- TxService (tx/service.proto) --------------------------------------------------------

// Transaction support: chain-tip feeds (blockhash, slot), transaction
// submission, and per-tx lifecycle status.
//
// Every RPC on this service requires a keypair-auth session token via
// `authorization: Bearer <token>` metadata. The token's maker_id is used
// for rate limiting on the public streams and to scope which signatures
// show up on the status stream.
service TxService {
  // Forward a transaction to upstream submission paths. Returns
  // the parsed signature immediately; landing/confirmation are reported
  // asynchronously on `SubscribeTxStatus`.
  rpc SubmitTx(SubmitTxRequest) returns (SubmitTxResponse);

  rpc SubscribeBlockhash(SubscribeBlockhashRequest) returns (stream BlockhashEvent);
  rpc SubscribeSlots(SubscribeSlotsRequest) returns (stream SlotEvent);
  rpc SubscribeTxStatus(SubscribeTxStatusRequest) returns (stream TxStatusEvent);
}

// -- HistoricalService (historical/service.proto) --------------------------------------------------------

// Historical (ClickHouse-backed) queries. Every RPC requires a session
// token via `authorization: Bearer <token>` metadata.
//
// Hard constraints enforced by the server:
//   - Maximum time window: 30 days.
//   - `GetTrades.limit` capped at 1000.
//   - `GetCandles` returns at most 10_000 rows per call.
//
// If the backing ClickHouse is not configured, every RPC returns
// FAILED_PRECONDITION.
service HistoricalService {
  rpc GetTrades(GetTradesRequest) returns (GetTradesResponse);
  rpc GetCandles(GetCandlesRequest) returns (GetCandlesResponse);
}

// -- Messages, events, and shared types ------------------------------

// -- Shared types (common.proto) --------------------------------------------------------

// Shared primitives used across every service: ids, solana-level crypto
// primitives, decimal wire format, enums, book level structs, and metadata.
// Every message in this package has `package sweetspot.api.v1;` so that the
// generated Rust module stays flat (`proto::<Type>`) regardless of which .proto
// a type was declared in.

// -- Ids -------------------------------------------------------------

message SpotId {
  uint64 id = 1;
}

message MakerId {
  uint64 id = 1;
}

message Pair {
  SpotId base = 1;
  SpotId quote = 2;
}

// -- Solana-level primitives -----------------------------------------

message Pubkey {
  bytes key = 1;
}

message Signature {
  bytes signature = 1;
}

message Blockhash {
  bytes hash = 1;
}

// -- Decimal ---------------------------------------------------------

// Human-readable decimal string (e.g. "155.14"). The client must parse
// with a decimal library that matches rust_decimal semantics.
message Decimal {
  string value = 1;
}

// -- Timestamp -------------------------------------------------------

// Unix microseconds since epoch. The single canonical timestamp unit
// across the entire API - events, responses, range queries. Field value
// `0` means "unspecified" (used on optional bounds in range queries and
// on absent event timestamps).
message Timestamp {
  uint64 micros = 1;
}

// -- Enums -----------------------------------------------------------

enum FeedLevel {
  FEED_LEVEL_UNSPECIFIED = 0;
  FEED_LEVEL_L1 = 1;
  FEED_LEVEL_L2 = 2;
  FEED_LEVEL_L3 = 3;
}

enum Side {
  SIDE_UNSPECIFIED = 0;
  SIDE_BUY = 1;
  SIDE_SELL = 2;
}

enum HealthState {
  HEALTH_STATE_UNSPECIFIED = 0;
  HEALTH_STATE_HEALTHY = 1;
  HEALTH_STATE_DEGRADED = 2;
  HEALTH_STATE_HALTED = 3;
}

// -- Book levels -----------------------------------------------------

message L2Level {
  Decimal price = 1;
  Decimal size = 2;
}

message L3Entry {
  MakerId maker_id = 1;
  Decimal price = 2;
  Decimal size = 3;
}

// -- Subscription descriptor -----------------------------------------

message PairSubscription {
  Pair pair = 1;
  FeedLevel level = 2;
  // When true, the server emits a full snapshot on every book update and
  // never sends deltas. When false (default), one snapshot on subscribe
  // followed by incremental deltas.
  bool snapshot_only = 3;
}

// -- Per-event metadata ----------------------------------------------

message PairMetadata {
  uint64 slot = 1;
  Timestamp ts = 2;
  uint64 update_id = 3;
}

// -- Reference / catalog ---------------------------------------------

message SpotMetadata {
  SpotId id = 1;
  string name = 2;
  Pubkey mint = 3;
  Pubkey program_id = 4;
  uint32 decimals = 5;
  uint32 atoms_per_lots = 6;
}

message SpotAssets {
  repeated SpotMetadata assets = 1;
}

message MakerMarketState {
  SpotId spot_id = 1;
  uint64 order_sequence = 2;
}

// Low-level per-spot balance sample, used by the indexer and by historical
// queries. `MakerBalanceEvent` is the streaming form.
message BalanceUpdate {
  SpotId spot_id = 1;
  MakerId maker_id = 2;
  // Token units (e.g. "1.5" SOL), not raw atoms.
  Decimal balance = 3;
  uint64 slot = 4;
}

// -- Health / status (cross-cutting) ---------------------------------

message StatusEvent {
  HealthState state = 1;
  // Absent = global scope, present = per-pair.
  Pair pair = 2;
  string reason = 3;
  Timestamp ts = 4;
}

// -- Auth messages (auth/messages.proto) --------------------------------------------------------

// -- AuthService messages --------------------------------------------
//
// Session-token flow:
//   1. Client calls `AuthService.Challenge(pubkey)` -> server returns a
//      single-use nonce bound to that pubkey with a short TTL.
//   2. Client signs AUTH_DOMAIN_PREFIX || nonce with the pubkey's keypair.
//   3. Client calls `AuthService.Authenticate(pubkey, signature)` -> server
//      verifies the signature covers the outstanding nonce, maps pubkey to
//      maker_id via MakerRegistry, and returns a bearer `session_token`.
//   4. Client attaches `authorization: Bearer <session_token>` metadata on
//      TxService or MakerService. Passwordless sessions are accepted by
//      MakerService only, not TxService.

message ChallengeRequest {
  // The pubkey (quoting authority) that will sign the challenge.
  Pubkey pubkey = 1;
}

message ChallengeResponse {
  // Single-use random nonce. Client signs AUTH_DOMAIN_PREFIX || nonce.
  bytes nonce = 1;
  // Absolute expiration. Server rejects Authenticate after this moment.
  Timestamp expires_at = 2;
}

message AuthenticateRequest {
  Pubkey pubkey = 1;
  // Signature over AUTH_DOMAIN_PREFIX || nonce from the outstanding Challenge.
  Signature signature = 2;
}

message AuthenticateResponse {
  // Opaque bearer token. Clients pass this back as
  // `authorization: Bearer <session_token>` metadata on all authenticated RPCs.
  string session_token = 1;
  // Resolved maker identity the token speaks for.
  MakerId maker_id = 2;
  // Absolute session expiration. Clients should re-auth before this
  // moment; server will start returning UNAUTHENTICATED once it passes.
  Timestamp expires_at = 3;
}

message RevokeRequest {
  string session_token = 1;
}

message RevokeResponse {}

message RequestLoginCodeRequest {
  string email = 1;
}

message RequestLoginCodeResponse {}

message VerifyLoginCodeRequest {
  string email = 1;
  string code = 2;
}

message VerifyLoginCodeResponse {
  // Opaque bearer token. Clients pass this back as
  // `authorization: Bearer <session_token>` metadata on MarketDataService.
  string session_token = 1;
  // Resolved maker identity associated with the user's organization.
  MakerId maker_id = 2;
  // Absolute session expiration.
  Timestamp expires_at = 3;
  string organization_name = 4;
}

// -- Market data events (market_data/events.proto) --------------------------------------------------------

// -- Book events -----------------------------------------------------

message L1Event {
  Pair pair = 1;
  PairMetadata metadata = 2;
  L2Level bid = 3;
  L2Level ask = 4;
}

message L2SnapshotEvent {
  Pair pair = 1;
  PairMetadata metadata = 2;
  repeated L2Level bids = 3;
  repeated L2Level asks = 4;
}

message L2UpdateEvent {
  Pair pair = 1;
  PairMetadata metadata = 2;
  repeated L2Level bids = 3;
  repeated L2Level asks = 4;
}

message L3SnapshotEvent {
  Pair pair = 1;
  PairMetadata metadata = 2;
  repeated L3Entry bids = 3;
  repeated L3Entry asks = 4;
}

message L3UpdateEvent {
  Pair pair = 1;
  PairMetadata metadata = 2;
  repeated L3Entry bids = 3;
  repeated L3Entry asks = 4;
}

// -- Cross-market snapshot -------------------------------------------

// `slot` is per-market: different entries may carry different slots when
// only a subset of markets updated in the most recent envelope.
message MarketSnapshot {
  Pair pair = 1;
  uint64 slot = 2;
  L2Level best_bid = 3;
  L2Level best_ask = 4;
  repeated L2Level bid_depth_levels = 5;
  repeated L2Level ask_depth_levels = 6;
  Decimal last_price = 7;
}

message MarketsSnapshotEvent {
  uint64 slot = 1;
  Timestamp ts = 2;
  repeated MarketSnapshot markets = 3;
}

// -- Fill events -----------------------------------------------------

message FillEvent {
  Pair pair = 1;
  Side side = 2;
  Decimal price = 3;
  Decimal size = 4;
  uint64 slot = 5;
  Timestamp ts = 6;
}

// -- Multiplexed stream envelope -------------------------------------
//
// MarketDataService.Subscribe returns a single stream carrying every
// per-pair book event plus cross-cutting StatusEvents. Fills are not
// carried here - use `MarketDataService.SubscribeFills`. Clients
// discriminate via `kind`.

message MarketDataEvent {
  oneof kind {
    L1Event l1 = 1;
    L2SnapshotEvent l2_snapshot = 2;
    L2UpdateEvent l2_update = 3;
    L3SnapshotEvent l3_snapshot = 4;
    L3UpdateEvent l3_update = 5;
    StatusEvent status = 6;
  }
}

// -- Market data messages (market_data/messages.proto) --------------------------------------------------------

// -- MarketDataService request / response ----------------------------

message SubscribeRequest {
  // Pairs to subscribe to with their desired feed level.
  repeated PairSubscription pairs = 1;
}

// Separate stream so book subscribers don't have to pay to deserialize
// fills and vice versa.
message SubscribeFillsRequest {
  // Per-pair filter. Empty = fills from every pair.
  repeated Pair pairs = 1;
}

message GetBookRequest {
  Pair pair = 1;
  FeedLevel level = 2;
}

message GetBookResponse {
  oneof snapshot {
    L2SnapshotEvent l2 = 1;
    L3SnapshotEvent l3 = 2;
    L1Event l1 = 3;
  }
}

message ListPairsRequest {}

message SubscribeMarketSnapshotsRequest {}

message ListedPair {
  Pair pair = 1;
  SpotMetadata base = 2;
  // Last landed fill price for this pair. Absent until the server observes a fill.
  Decimal last_price = 3;
  SpotMetadata quote = 4;
}

message ListPairsResponse {
  repeated ListedPair pairs = 1;
}

// -- Maker events (maker/events.proto) --------------------------------------------------------

// Streaming form of a per-maker balance observation. Emitted whenever the
// underlying on-chain account updates. Always scoped to the authenticated
// maker - servers filter by the token's maker_id before fan-out, so a
// client never sees another maker's balances.
message MakerBalanceEvent {
  MakerId maker_id = 1;
  SpotId spot_id = 2;
  // Token units (e.g. "1.5" SOL); server scales by SpotMetadata.decimals.
  Decimal balance = 3;
  uint64 slot = 4;
  Timestamp ts = 5;
  Decimal notional = 6;
  Decimal soft_max_balance = 7;
}

// -- Per-maker market snapshot ---------------------------------------

// The authenticated maker's resting quote on one side of a pair plus
// its 0-indexed L2 depth level (0 = top of book). Aggregates across the
// maker's individual orders at their best price for that side.
message MakerQuote {
  Decimal price = 1;
  Decimal size = 2;
  uint32 level = 3;
}

// Per-pair view that augments `MarketSnapshot` with the authenticated
// maker's own best bid / best ask + depth level.
message MakerMarketSnapshot {
  Pair pair = 1;
  uint64 slot = 2;
  L2Level best_bid = 3;
  L2Level best_ask = 4;
  repeated L2Level bid_depth_levels = 5;
  repeated L2Level ask_depth_levels = 6;
  Decimal last_price = 7;
  // Absent when the maker has no order on this side of this pair.
  MakerQuote maker_bid = 8;
  MakerQuote maker_ask = 9;
}

message MakerMarketsSnapshotEvent {
  uint64 slot = 1;
  Timestamp ts = 2;
  repeated MakerMarketSnapshot markets = 3;
}

// -- Per-maker fill --------------------------------------------------

// Fill where the authenticated maker provided the liquidity. The `side`
// here is the maker's side (BUY = maker bought, SELL = maker sold) -
// the inverse of `FillEvent.side` on the public market-data stream,
// which carries the taker's side.
message MakerFillEvent {
  Pair pair = 1;
  MakerId maker_id = 2;
  Side side = 3;
  Decimal price = 4;
  Decimal size = 5;
  uint64 slot = 6;
  Timestamp ts = 7;
}

// -- Stats messages (stats/messages.proto) --------------------------------------------------------

// -- Rolling-window enums --------------------------------------------

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

// -- Shared building blocks ------------------------------------------

// Per-pair volume + fill count over a single rolling window.
// `volume_quote = sum(size * price)` in the pair's quote asset.
message WindowStats {
  StatsWindow window = 1;
  Decimal volume_base = 2;
  Decimal volume_quote = 3;
  uint64 fill_count = 4;
}

// Cross-venue equivalent of `WindowStats`. `volume_base` is dropped - summing
// base across pairs with different base assets mixes denominations.
message CrossVenueWindowStats {
  StatsWindow window = 1;
  Decimal volume_quote = 2;
  uint64 fill_count = 3;
}

// 1h / 24h / 30d bundled together. All three are always populated even
// if a pair has no fills in the smaller windows (zero is meaningful).
message PairVolume {
  Pair pair = 1;
  WindowStats one_hour = 2;
  WindowStats twenty_four_hour = 3;
  WindowStats thirty_day = 4;
  // Most recent landed fill price. Absent if no fills observed yet.
  Decimal last_price = 5;
}

// -- GetSummary ------------------------------------------------------

message GetSummaryRequest {}

message GetSummaryResponse {
  // How many pairs the indexer has discovered and is streaming books for.
  uint32 active_pairs = 1;
  // How many makers are registered on-chain.
  uint32 active_makers = 2;

  // Cross-venue aggregates over each window - quote volume + fill count
  // totalled across every pair.
  CrossVenueWindowStats one_hour = 3;
  CrossVenueWindowStats twenty_four_hour = 4;
  CrossVenueWindowStats thirty_day = 5;

  // All-time totals, computed from the events table since the archiver
  // started recording.
  Decimal volume_quote_all_time = 7;
  uint64 fill_count_all_time = 8;

  // Distinct taker accounts seen in the last 24h. A rough proxy for active
  // users.
  uint32 unique_traders_24h = 9;

  // Largest single USDC-quoted trade in the last 24h by notional value
  // (`size * price`). Absent if no USDC-quoted fills have happened.
  LargestTrade largest_trade_24h = 10;

  // Cross-venue order-update rate - every observed change to a pair's
  // virtual book (maker cancel/replace plus oracle moves).
  UpdateRate update_rate = 11;

  // Cross-venue oracle-update rate - counts observed oracle fair changes
  // across every spot market.
  UpdateRate oracle_update_rate = 12;
}

message LargestTrade {
  Pair pair = 1;
  Decimal size = 2;
  Decimal price = 3;
  Timestamp ts = 4;
  // `size * price`, in USDC for summary responses.
  Decimal notional = 5;
}

// -- GetVolumeBreakdown ----------------------------------------------

message GetVolumeBreakdownRequest {
  // Optional pair filter. Empty = every pair the venue has fills for in
  // the largest window (30d).
  repeated Pair pairs = 1;
}

message GetVolumeBreakdownResponse {
  // Ordered by 24h volume descending.
  repeated PairVolume pairs = 1;
}

// -- GetVolumeSeries -------------------------------------------------

message GetVolumeSeriesRequest {
  // Optional pair filter. Absent = cross-venue aggregate.
  Pair pair = 1;
  // Window selects both the time horizon and the bucket size:
  //   1H  -> 60 x 1m buckets
  //   24H -> 288 x 5m buckets
  //   30D -> 720 x 1h buckets
  StatsWindow window = 2;
}

message VolumeBucket {
  // Bucket start (inclusive).
  Timestamp ts = 1;
  Decimal volume_base = 2;
  Decimal volume_quote = 3;
  uint64 fill_count = 4;
}

message GetVolumeSeriesResponse {
  // Ordered oldest-first.
  repeated VolumeBucket buckets = 1;
  // Width of each bucket in seconds - clients can label the X axis
  // without having to repeat the server's window->interval mapping.
  uint32 bucket_seconds = 2;
}

// -- GetAssetTvls ----------------------------------------------------

message GetAssetTvlsRequest {}

message AssetTvl {
  SpotMetadata asset = 1;
  // Sum of every maker's balance for this spot, in token units (e.g.
  // "1500.5" SOL - not atoms).
  Decimal total_balance = 2;
  // How many makers currently hold a non-zero balance of this asset.
  uint32 maker_count = 3;
}

message GetAssetTvlsResponse {
  repeated AssetTvl assets = 1;
}

// -- UpdateRate ------------------------------------------------------

// Rolling event rate. Used for both order updates and oracle updates on
// `GetSummaryResponse`.
message UpdateRate {
  // Updates within the trailing 1 second.
  double per_second_1s = 1;
  // Average updates / second over the trailing 1 minute.
  double per_second_1m = 2;
  // Average updates / second over the trailing 5 minutes.
  double per_second_5m = 3;
}

// -- Maker messages (maker/messages.proto) --------------------------------------------------------

// -- MakerService request / response ---------------------------------

message SubscribeBalanceRequest {
  // Optional per-spot filter. Empty = every spot the authenticated maker
  // has a balance in (base + quote of every pair they quote on).
  repeated SpotId spot_ids = 1;
}

message GetBalanceRequest {
  // Optional per-spot filter; empty = all spots.
  repeated SpotId spot_ids = 1;
}

message GetBalanceResponse {
  repeated MakerBalanceEvent balances = 1;
}

message SubscribeMakerMarketSnapshotsRequest {}

// Per-maker volume breakdown. The authenticated maker_id is taken from
// the bearer token; clients only optionally narrow the pair set.
message GetMakerVolumeBreakdownRequest {
  // Optional pair filter. Empty = every pair the maker has fills in
  // across the largest window (30d).
  repeated Pair pairs = 1;
}

message GetMakerVolumeBreakdownResponse {
  // Ordered by 24h volume descending.
  repeated PairVolume pairs = 1;
}

message SubscribeMakerFillsRequest {
  // Optional per-pair filter. Empty = every pair the authenticated maker
  // gets a fill on.
  repeated Pair pairs = 1;
}

// Per-maker volume series. The authenticated maker_id is taken from the
// bearer token. Window selects both horizon and bucket width identically
// to `StatsService.GetVolumeSeries`.
message GetMakerVolumeSeriesRequest {
  // Optional pair filter. Absent = aggregate across every pair the maker
  // has fills in.
  Pair pair = 1;
  StatsWindow window = 2;
}

message GetMakerVolumeSeriesResponse {
  // Ordered oldest-first.
  repeated VolumeBucket buckets = 1;
  uint32 bucket_seconds = 2;
}

// -- GetNavHistory ----------------------------------------------

// Per-maker historical NAV (net asset value, USD). The authenticated
// maker_id is taken from the bearer token. Window selects both horizon and
// bucket width identically to `GetVolumeSeries`.
message GetNavHistoryRequest {
  StatsWindow window = 1;
}

message NavPoint {
  Timestamp ts = 1;
  // Total USD value of the maker's holdings in this bucket. Held assets with
  // no available price are valued at 0; `unpriced_assets` flags when that
  // makes the total a partial valuation.
  Decimal nav_usd = 2;
  // Freshness of the prices the NAV was computed from. Lets clients spot a
  // valuation taken against carried-forward (stale) prices.
  Timestamp price_as_of = 3;
  // Held assets that were priced into `nav_usd`.
  uint32 priced_assets = 4;
  // Held assets with no price, counted as 0 in `nav_usd`. Non-zero means the
  // total understates true NAV.
  uint32 unpriced_assets = 5;
}

message GetNavHistoryResponse {
  // Ordered oldest-first.
  repeated NavPoint points = 1;
  uint32 bucket_seconds = 2;
}

// -- GetStats --------------------------------------------------------

message GetMakerStatsRequest {}

message GetMakerStatsResponse {
  // Echoed from the bearer token so the client can sanity-check the scope.
  MakerId maker_id = 1;

  // Match events where this maker provided liquidity.
  UpdateRate fills = 4;

  // Transactions accepted from this maker by the server's TxService
  // (counts every TX whose initial outcome was `Submitted`).
  UpdateRate transactions_sent = 5;
  // Transactions confirmed on-chain (TxOutcome::Confirmed).
  UpdateRate transactions_landed = 6;
  // Transactions that failed - rejected pre-submit, expired before landing,
  // or landed with an error.
  UpdateRate transactions_failed = 7;

  // Venue-wide oracle-fair update rate. Same value every maker observes -
  // included so MMs can compare their request rate against oracle volatility.
  UpdateRate oracle_updates = 8;
}

// -- Tx events (tx/events.proto) --------------------------------------------------------

// -- Chain-tip data --------------------------------------------------

message BlockhashEvent {
  Blockhash blockhash = 1;
  // Server's current priority-fee recommendation (microlamports per CU)
  // sampled from recent landed txs. Zero = no recommendation yet.
  uint64 recommended_cu_price = 2;
  Timestamp ts = 3;
}

message SlotEvent {
  uint64 slot = 1;
  Timestamp ts = 2;
}

// -- Per-tx lifecycle ------------------------------------------------

// Server has accepted the tx for submission (pre-confirmation).
message TxAckEvent {
  Signature signature = 1;
  Timestamp ts = 2;
}

// Tx landed and did not error.
message TxConfirmedEvent {
  Signature signature = 1;
  uint64 slot = 2;
  Timestamp ts = 3;
}

// Tx landed but reverted, or timed out before landing.
message TxFailedEvent {
  Signature signature = 1;
  string reason = 2;
  Timestamp ts = 3;
}

// Multiplexed tx-status stream envelope.
message TxStatusEvent {
  oneof kind {
    TxAckEvent ack = 1;
    TxConfirmedEvent confirmed = 2;
    TxFailedEvent failed = 3;
  }
}

// -- Tx messages (tx/messages.proto) --------------------------------------------------------

// -- TxService request / response ------------------------------------

// Submit a fully signed Solana transaction. The server forwards the tx
// to its upstream RPC/jito paths; lifecycle events (ack/confirmed/failed)
// arrive on `SubscribeTxStatus`.
message SubmitTxRequest {
  // Serialized signed transaction bytes - legacy or v0. The server does
  // not modify the tx; fee payer, recent blockhash, compute budget, and
  // priority fee are all the client's choices.
  bytes transaction = 1;
}

message SubmitTxResponse {
  // Parsed signature from the submitted tx. Use this to correlate with
  // subsequent TxAckEvent / TxConfirmedEvent / TxFailedEvent received
  // on `SubscribeTxStatus`.
  Signature signature = 1;
  // Server wall-clock timestamp the submission was accepted.
  Timestamp ts = 2;
}

// Retrieve the set of fee-payer pubkeys the server will pay tx fees for.
// Clients may use any returned pubkey as the `feePayer` of a submitted tx;
// the server then covers the SOL cost on landing.
message GetSponsoredPayersRequest {}

message GetSponsoredPayersResponse {
  repeated Pubkey payers = 1;
}

message SubscribeBlockhashRequest {}

message SubscribeSlotsRequest {}

// Server streams status transitions for any tx submitted under the
// authenticated maker's session. No client-side filter - the maker_id
// on the session token scopes the stream.
message SubscribeTxStatusRequest {}

// -- Historical messages (historical/messages.proto) --------------------------------------------------------

// -- Historical primitives -------------------------------------------

enum CandleInterval {
  CANDLE_INTERVAL_UNSPECIFIED = 0;
  CANDLE_INTERVAL_1M = 1;
  CANDLE_INTERVAL_5M = 2;
  CANDLE_INTERVAL_15M = 3;
  CANDLE_INTERVAL_30M = 4;
  CANDLE_INTERVAL_1H = 5;
  CANDLE_INTERVAL_4H = 6;
  CANDLE_INTERVAL_1D = 7;
}

message Candle {
  // Candle open time.
  Timestamp ts = 1;
  Decimal open = 2;
  Decimal high = 3;
  Decimal low = 4;
  Decimal close = 5;
  // Base-asset volume over the interval.
  Decimal volume = 6;
}

// Historical trade row. Same shape as the live `FillEvent`.
message Trade {
  Pair pair = 1;
  Side side = 2;
  Decimal price = 3;
  Decimal size = 4;
  uint64 slot = 5;
  Timestamp ts = 6;
}

// -- GetTrades -------------------------------------------------------

message GetTradesRequest {
  Pair pair = 1;
  // Inclusive lower bound. Unset / zero-micros = unbounded (server uses
  // its retention window).
  Timestamp start = 2;
  // Exclusive upper bound. Unset / zero-micros = now.
  Timestamp end = 3;
  // Max rows to return. Zero = server default (50). Hard cap 1000.
  uint32 limit = 4;
}

message GetTradesResponse {
  // Ordered newest-first.
  repeated Trade trades = 1;
}

// -- GetCandles ------------------------------------------------------

message GetCandlesRequest {
  Pair pair = 1;
  CandleInterval interval = 2;
  // Inclusive lower bound.
  Timestamp start = 3;
  // Exclusive upper bound. Unset / zero-micros = now.
  Timestamp end = 4;
}

message GetCandlesResponse {
  // Ordered oldest-first. Hard cap of 10_000 candles per response.
  repeated Candle candles = 1;
}

Build on Solana