Saltar a contenido

Gateway API Reference

This document describes the gRPC API exposed by the Almanak Gateway.

Services Overview

Service Methods Description
Health 3 Standard gRPC health checks and chain registration
MarketService 10 Price data, provider-exact reference prices, Pendle PT/YT-USD price, balances, batch balances, technical indicators, Uniswap V4 pool key lookup, and verified lending/perpetual-market resolution
StateService 31 Strategy state persistence, portfolio snapshots/metrics, transaction ledger, accounting events, position events, accounting outbox, atomic ledger+registry writes, and cutover migration state
ExecutionService 3 Intent compilation and transaction execution
ObserveService 4 Logging, alerts, metrics, and timeline events
RpcService 7 JSON-RPC proxy to blockchains with typed queries
IntegrationService 14 Third-party data (Binance, CoinGecko, TheGraph, CoinGecko Onchain, Zerion)
DashboardService 22 Operator dashboard data, actions, transaction ledger, PnL/cost stack, audit posture, trade tape, activity feed, positions, reconciliation report, and operator reconciliation actions
FundingRateService 2 Perpetual funding rates and spreads
SimulationService 1 Transaction bundle simulation (Tenderly/Alchemy)
PoolAnalyticsService 2 DEX pool analytics (TVL, volume, fees) + product-distinct token-pool listing
PoolHistoryService 1 Historical pool snapshots (TVL, volume, fees over time). Disabled by default; hosted rollout enables via ALMANAK_GATEWAY_POOL_HISTORY_ENABLED=true deployment config
RateHistoryService 12 Lending APY (live + historical), perp funding and venue-native index-price history, DEX TWAP (single + series), exact-pool state series, DEX LWAP (liquidity-weighted spot), DEX volume history, gas prices, and provider-exact oracle prices (current + historical). Strategy-side RateMonitor / backtesting rate providers are thin gRPC clients of this service.
PolymarketService 20 Polymarket CLOB API proxy (market data, orders, positions, price history, trade tape)
EnsoService 4 Enso Finance routing and bundling
TokenService 4 Token resolution and on-chain metadata
LifecycleService 6 Agent state management, heartbeat, and commands
TeardownService 23 Hosted teardown state routing (V2 deployments): teardown requests, execution state, and operator approvals
PositionService 1 Position registry reconciliation against on-chain truth (T24 / VIB-4210)
PerpFillService 3 Executed perp fills, funding settlements, and single-order status (by cloid) for a wallet; backs the perp accounting path and the fill-reconciliation reject-detection pump (VIB-5595 / VIB-5616)

Health

Check

Check if the service is healthy (liveness probe).

rpc Check(HealthCheckRequest) returns (HealthCheckResponse);

Watch

Watch for health status changes (streaming readiness probe).

rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);

RegisterChains

Pre-initialize execution orchestrators and compilers for specified chains. Call this at startup to warm up chain-specific resources.

rpc RegisterChains(RegisterChainsRequest) returns (RegisterChainsResponse);

Request:

message RegisterChainsRequest {
  repeated string chains = 1;      // Chain names to pre-initialize (e.g., "arbitrum", "base")
  string wallet_address = 2;       // Wallet address for orchestrator initialization
}

Response:

message RegisterChainsResponse {
  bool success = 1;
  repeated string initialized_chains = 2;  // Chains successfully initialized
  string wallet_address = 3;               // Wallet address derived from gateway private key
  string error = 4;
  map<string, string> chain_wallets = 5;   // Per-chain wallet addresses resolved from wallet registry
}

MarketService

GetPrice

Get the current price of a token.

rpc GetPrice(PriceRequest) returns (PriceResponse)

Request:

message PriceRequest {
  string token = 1;      // Token symbol or address
  string quote = 2;      // Quote currency (default: "USD")
}

Response:

message PriceResponse {
  string price = 1;          // Decimal as string (precision preserved)
  int64 timestamp = 2;
  string source = 3;
  double confidence = 4;     // 0.0-1.0
  bool stale = 5;
}

Example:

from almanak.framework.data.price import GatewayPriceOracle

oracle = GatewayPriceOracle(gateway_client)
price = await oracle.get_price("ETH", "USD")

GetReferencePrice

Get a named non-crypto reference price from one gateway-verified feed. Unlike GetPrice, this RPC never aggregates same-symbol crypto assets. The gateway owns the exact feed selection, provider timestamp and heartbeat evaluation, and reference-market session status. Unsupported pairs and unavailable, malformed, stale, or errored observations fail closed without a numeric price.

rpc GetReferencePrice(ReferencePriceRequest) returns (ReferencePriceResponse)

Request:

message ReferencePriceRequest {
  string instrument = 1;  // e.g. XAU
  string quote = 2;       // USD only in the initial contract
  string chain = 3;       // Chain hosting the verified feed
}

Response:

message ReferencePriceResponse {
  string instrument = 1;
  string quote = 2;
  string chain = 3;
  string price = 4;                         // Empty when unavailable; never "0" as a sentinel
  ReferencePriceAvailability availability = 5;
  double confidence = 6;
  string source = 7;                        // Provider, pair, and verified feed address
  int64 observed_at = 8;                    // Provider updatedAt, not gateway wall clock
  bool stale = 9;                           // Evaluated against the provider heartbeat
  ReferenceMarketStatus market_status = 10;
  int64 market_status_as_of = 11;
  string market_status_source = 12;
  string reason = 13;
}

Availability and market status:

enum ReferencePriceAvailability {
  REFERENCE_PRICE_AVAILABILITY_UNSPECIFIED = 0;
  REFERENCE_PRICE_AVAILABILITY_AVAILABLE = 1;
  REFERENCE_PRICE_AVAILABILITY_UNMEASURED = 2;
  REFERENCE_PRICE_AVAILABILITY_ERRORED = 3;
}

enum ReferenceMarketStatus {
  REFERENCE_MARKET_STATUS_UNSPECIFIED = 0;
  REFERENCE_MARKET_STATUS_OPEN = 1;
  REFERENCE_MARKET_STATUS_CLOSED = 2;
  REFERENCE_MARKET_STATUS_UNKNOWN = 3;
}

Only REFERENCE_PRICE_AVAILABILITY_AVAILABLE carries a measured price. Clients must also enforce their own maximum age and require an open reference market before using the observation to authorize a trade.

GetPtPrice

Get the composed USD price of a Pendle PT (Principal Token) or YT (Yield Token).

The gateway is the price authority: PT/USD is composed gateway-side as pt_to_asset_rate × underlying/USD (it is not fetched from a direct Pendle feed). The PT/YT symbol is the canonical identity and cross-boundary join key.

rpc GetPtPrice(PtPriceRequest) returns (PtPriceResponse)

Request:

message PtPriceRequest {
  string symbol = 1;       // Canonical PT/YT symbol — the identity + join key
  string chain = 2;
  string quote = 3;        // Quote currency (default: "USD")
  int64 maturity_ts = 4;   // Optional maturity hint; 0 = gateway resolves it
}

Response:

message PtPriceResponse {
  string symbol = 1;
  string chain = 2;
  string quote = 3;
  string price = 4;                          // Decimal as string; ABSENT (empty) when unmeasured — never "0"
  PtPriceAvailability availability = 5;       // Wire-level Empty != Zero (see below)
  double confidence = 6;                     // 0.0-1.0
  PtPriceConfidenceBand confidence_band = 7;  // Coarse band — combine with `stale` (field 12) for ValueConfidence
  string underlying_price = 8;               // Composition transparency (optional)
  string pt_to_asset_rate = 9;               // Composition transparency (optional)
  string source = 10;                        // e.g. "composition:getPtToAssetRate×<oracle>"
  int64 timestamp = 11;
  bool stale = 12;
  int64 maturity_ts = 13;
  int32 days_to_maturity = 14;
}

Availability (Empty ≠ Zero): price is a measured number only when availability == AVAILABLE. Any other state carries an empty price string — never the literal "0" (which would be a measured zero). Consumers gate on availability, not on string emptiness, and must treat the zero-value enum as unmeasured (fail closed):

enum PtPriceAvailability {
  PT_PRICE_AVAILABILITY_UNSPECIFIED = 0;  // old gateway / unknown -> UNMEASURED (fail closed)
  PT_PRICE_AVAILABILITY_AVAILABLE = 1;    // composed OK -> price is MEASURED
  PT_PRICE_AVAILABILITY_UNMEASURED = 2;   // expected: PT not priceable + no composition leg -> NO price
  PT_PRICE_AVAILABILITY_ERRORED = 3;      // a read raised -> NO price
}

enum PtPriceConfidenceBand {
  PT_PRICE_CONFIDENCE_BAND_UNSPECIFIED = 0;  // old gateway / unknown -> UNAVAILABLE
  PT_PRICE_CONFIDENCE_BAND_HIGH = 1;          // all inputs measured
  PT_PRICE_CONFIDENCE_BAND_ESTIMATED = 2;     // a fallback was used (e.g. rate defaulted at-par)
  PT_PRICE_CONFIDENCE_BAND_UNAVAILABLE = 3;   // cannot be valued
}

Confidence band ≠ ValueConfidence (not 1:1): PtPriceConfidenceBand carries only the three origination bands (HIGH / ESTIMATED / UNAVAILABLE). The framework ValueConfidence enum has a fourth member, STALE, which is not a band here — staleness is carried separately by the stale bool (field 12). Consumers MUST combine confidence_band and stale to derive the final ValueConfidence; mapping band→enum directly would silently render a stale price as HIGH and drop the staleness signal.

GetBalance

Get token balance for a wallet.

rpc GetBalance(BalanceRequest) returns (BalanceResponse)

Request:

message BalanceRequest {
  string token = 1;            // Token symbol or address
  string chain = 2;
  string wallet_address = 3;
}

Response:

message BalanceResponse {
  string balance = 1;          // Human-readable units as string
  string balance_usd = 2;
  string address = 3;
  int32 decimals = 4;
  string raw_balance = 5;      // Wei/raw units as string
  int64 timestamp = 6;
  bool stale = 7;
  string error = 8;
}

BatchGetBalances

Get token balances across multiple chains in a single call.

rpc BatchGetBalances(BatchBalanceRequest) returns (BatchBalanceResponse)

Request:

message BatchBalanceRequest {
  repeated BalanceRequest requests = 1;
}

Response:

message BatchBalanceResponse {
  repeated BalanceResponse responses = 1;
}

GetIndicator

Calculate a technical indicator.

rpc GetIndicator(IndicatorRequest) returns (IndicatorResponse)

LookupV4PoolKey

Resolve a Uniswap V4 bytes32 pool id back to its structured PoolKey. Useful for receipt parsers and indexers that observe on-chain events whose log payload carries the bytes32 id rather than the structured key. The gateway populates the cache from observed PoolManager.Initialize events; unknown ids return NOT_FOUND.

rpc LookupV4PoolKey(LookupV4PoolKeyRequest) returns (LookupV4PoolKeyResponse)

ListLendingMarkets

Verified lending-market resolution (VIB-5985), Morpho-first via the connector-owned GatewayLendingMarketDiscoveryCapability — no protocol branches in the servicer; an unsupported protocol returns INVALID_ARGUMENT listing the supported ones. Returns ALL curated-catalog CANDIDATES matching the filters (paginated). It NEVER ranks or auto-picks: zero matches is an explicit empty page the caller must treat as an error to act on, and multiple matches means the caller chooses. Token filters are address-resolved before matching (symbols are spoofable). Candidate records are verified=false / source=CURATED_CATALOG — the curated catalog is a candidate source, not proof; verify a chosen id with GetLendingMarket before pinning it into a config.

rpc ListLendingMarkets(ListLendingMarketsRequest) returns (ListLendingMarketsResponse)

Request:

message ListLendingMarketsRequest {
  string protocol = 1;          // connector slug (e.g. "morpho_blue")
  string chain = 2;
  string collateral_token = 3;  // symbol OR address; empty = no filter
  string loan_token = 4;        // symbol OR address; empty = no filter
  int64 lltv_bps = 5;           // exact LLTV filter in bps; 0 = no filter
  int32 page_size = 6;          // 0 = server default
  string page_token = 7;        // opaque continuation token; empty = first page
}

Response:

message ListLendingMarketsResponse {
  repeated LendingMarket markets = 1;  // ALL matches on this page; never auto-ranked
  string next_page_token = 2;          // empty when this is the last page
  int32 total_matches = 3;             // total matches across all pages
  bool success = 4;
  string error = 5;
}

GetLendingMarket

Verifies one exact market_id on-chain. The gateway reads idToMarketParams(id) through its own RPC path, RECOMPUTES the market id from the returned params (keccak of the params tuple, exactly as the protocol defines it) and compares it to the requested id. Only a match returns verified=true / source=ONCHAIN_VERIFY; a recompute mismatch is a hard INVALID_ARGUMENT error and a non-existent market is NOT_FOUND — never a fabricated record. This is the only surface that may promote a market_id into a config. Immutable market params only (tokens, oracle, IRM, LLTV) — live supply/borrow state is out of scope.

rpc GetLendingMarket(GetLendingMarketRequest) returns (LendingMarketResponse)

Request:

message GetLendingMarketRequest {
  string protocol = 1;
  string chain = 2;
  string market_id = 3;  // exact bytes32 hex to verify on-chain
}

Response:

message LendingMarketResponse {
  LendingMarket market = 1;  // populated only when success && verified
  bool success = 2;
  string error = 3;
}

The shared LendingMarket message (Empty ≠ Zero: string fields absent when unresolvable, never fabricated):

message LendingMarket {
  LendingMarketKind kind = 1;    // Morpho = ISOLATED_PAIR
  string protocol = 2;
  string chain = 3;
  string market_id = 4;
  string collateral_token = 5;   // address; absent if n/a
  string collateral_symbol = 6;  // absent when unresolvable
  string loan_token = 7;
  string loan_symbol = 8;
  int64 lltv_bps = 9;            // 9150 = 91.5%
  string oracle = 10;
  string irm = 11;
  bool verified = 12;            // true only after on-chain recompute+compare
  LendingMarketSource source = 13;
}

GetPerpMarket

Resolves one perpetual market through the connector-owned discovery capability and verifies its market/index/long/short token tuple on-chain before returning it. Exact market addresses and connector-defined labels are accepted. An unknown market returns NOT_FOUND; unsupported routing or ambiguous/unverified metadata fails closed.

rpc GetPerpMarket(GetPerpMarketRequest) returns (PerpMarketResponse)

Request:

message GetPerpMarketRequest {
  string protocol = 1;  // connector slug (for example, "gmx_v2")
  string chain = 2;
  string market = 3;    // canonical label, full venue name, or exact address
}

Response:

message PerpMarketResponse {
  PerpMarket market = 1;  // populated only after on-chain tuple verification
  bool success = 2;
  string error = 3;
}

message PerpMarket {
  string protocol = 1;
  string chain = 2;
  string label = 3;
  string market_token = 4;
  string index_token = 5;
  string index_symbol = 6;
  int32 index_token_decimals = 7;
  string long_token = 8;
  string long_token_symbol = 9;
  string short_token = 10;
  string short_token_symbol = 11;
  bool verified = 12;
}

StateService

LoadState

Load strategy state from storage.

rpc LoadState(LoadStateRequest) returns (StateData)

Request:

message LoadStateRequest {
  string deployment_id = 1;
}

Response:

message StateData {
  string deployment_id = 1;
  int64 version = 2;
  bytes data = 3;              // JSON-serialized state
  int32 schema_version = 4;
  string checksum = 5;         // SHA-256 hex
  int64 created_at = 6;
  int64 updated_at = 7;
  string loaded_from = 8;      // "hot", "warm"
}

SaveState

Save strategy state to storage.

rpc SaveState(SaveStateRequest) returns (SaveStateResponse)

Request:

message SaveStateRequest {
  string deployment_id = 1;
  int64 expected_version = 2;  // For optimistic locking (0 = new state)
  bytes data = 3;              // JSON-serialized state
  int32 schema_version = 4;
}

Response:

message SaveStateResponse {
  bool success = 1;
  int64 new_version = 2;
  string error = 3;
  string checksum = 4;
}

DeleteState

Delete strategy state.

rpc DeleteState(DeleteStateRequest) returns (DeleteStateResponse)

SavePortfolioSnapshot

Save a portfolio snapshot for tracking valuation over time.

rpc SavePortfolioSnapshot(SaveSnapshotRequest) returns (SaveSnapshotResponse)

GetLatestSnapshot

Get the most recent portfolio snapshot.

rpc GetLatestSnapshot(GetLatestSnapshotRequest) returns (SnapshotData)

GetSnapshotsSince

Get all portfolio snapshots since a given timestamp.

rpc GetSnapshotsSince(GetSnapshotsSinceRequest) returns (SnapshotList)

SavePortfolioMetrics

Legacy update lane for an already-established metrics row. An omitted positions field preserves that row's complete stored list. A present markerless full list may replace only an existing markerless row when the initial value is identical; it cannot touch a sealed row. Neither shape can establish a row or add/remove provenance. Provenance-bearing requests are rejected and current sealed clients use the version-fenced method below.

rpc SavePortfolioMetrics(SaveMetricsRequest) returns (SaveMetricsResponse)

SavePortfolioMetricsWithProvenance

Version-fenced save for a metrics baseline carrying immutable provenance. A client first checks PortfolioMetricsData.baseline_provenance_supported; an older gateway cannot dispatch this RPC and returns UNIMPLEMENTED without entering the legacy mutation handler. Every first row must carry exactly one valid provenance record.

rpc SavePortfolioMetricsWithProvenance(SaveMetricsRequest) returns (SaveMetricsResponse)

GetPortfolioMetrics

Retrieve stored portfolio metrics.

rpc GetPortfolioMetrics(GetMetricsRequest) returns (PortfolioMetricsData)

SaveLedgerEntry

Persist a single structured trade record to the transaction ledger.

rpc SaveLedgerEntry(SaveLedgerEntryRequest) returns (SaveLedgerEntryResponse)

Request:

message SaveLedgerEntryRequest {
  string id = 1;                   // UUID primary key (idempotent ON CONFLICT target)
  string cycle_id = 2;
  string deployment_id = 3;
  string deployment_id = 4;
  string execution_mode = 5;       // "live" | "paper" | "dry_run"
  int64 timestamp = 6;             // Unix epoch seconds
  string intent_type = 7;
  string token_in = 8;
  string amount_in = 9;            // Decimal string
  string token_out = 10;
  string amount_out = 11;          // Decimal string
  string effective_price = 12;     // Decimal string, "" when not applicable
  optional double slippage_bps = 13;
  int64 gas_used = 14;
  string gas_usd = 15;             // Decimal string, "" when unknown
  string tx_hash = 16;
  string chain = 17;
  string protocol = 18;
  bool success = 19;
  string error = 20;
  bytes extracted_data_json = 21;  // Serialised extracted_data dict
}

Response:

message SaveLedgerEntryResponse {
  bool success = 1;
  string error = 2;
}

GetLedgerEntry

Retrieve a single ledger entry by id (for re-derivation and audit lookups).

rpc GetLedgerEntry(GetLedgerEntryRequest) returns (GetLedgerEntryResponse)

SumLedgerGasUsd

Sum transaction_ledger.gas_usd across a deployment for the portfolio_metrics.gas_spent_usd aggregate (VIB-4247). The gateway performs the read because hosted strategy containers cannot access Postgres directly.

rpc SumLedgerGasUsd(SumLedgerGasUsdRequest) returns (SumLedgerGasUsdResponse)

GetLedgerEntriesMeasured

Read a deployment's transaction_ledger rows WITH a measured backend_status signal (VIB-5416). The teardown swap-back clamp folds NO_ACCOUNTING ledger rows (STAKE→wstETH, WRAP→WETH, CDP MINT→stablecoin — primitives that write a ledger row but no accounting event) into its tracked inventory. Mirrors GetAccountingEvents' Empty ≠ Zero contract: an ABSENT/ERRORED/UNSPECIFIED status is UNMEASURED, so the clamp drops the NO_ACCOUNTING lane (the token strands — the safe under-sweep direction) rather than treating an empty read as measured-zero. limit=0 means full history. Read-only; no stored column.

rpc GetLedgerEntriesMeasured(GetLedgerEntriesMeasuredRequest) returns (GetLedgerEntriesMeasuredResponse)

SaveAccountingEvent

Persist a typed accounting event (Layer 5 accounting). Writers must route through the accounting outbox; direct calls from connectors or the runner hot path are forbidden (see docs/internal/blueprints/27-accounting.md).

rpc SaveAccountingEvent(SaveAccountingEventRequest) returns (SaveAccountingEventResponse)

GetAccountingEvents

Retrieve typed accounting events for a strategy or ledger entry.

rpc GetAccountingEvents(GetAccountingEventsRequest) returns (GetAccountingEventsResponse)

HasAccountingEventsForLedger

Idempotency check: returns whether typed events already exist for a given ledger entry (used by the outbox processor to avoid double-writes).

rpc HasAccountingEventsForLedger(HasAccountingEventsForLedgerRequest) returns (HasAccountingEventsForLedgerResponse)

SavePositionEvent

Persist an LP or perp position lifecycle event (open/close/collect-fees). Lending intents (SUPPLY, BORROW, REPAY, WITHDRAW) are intentionally not routed through this RPC — lending positions are fungible (no stable position_id) and are tracked via Layer 5 typed accounting events instead.

rpc SavePositionEvent(SavePositionEventRequest) returns (SavePositionEventResponse)

SavePositionStateSnapshots

Persist per-iteration position state snapshots (tied to a parent portfolio_snapshots.id, one row per open position). Backs accountant cells G14 / G15 / L2 / L3 / L5. Non-blocking writes — logged and swallowed in non-live modes; raises AccountingPersistenceError in live via the runner's mode-aware caller. The hosted Postgres path returns UNIMPLEMENTED until the metrics-database migration adds position_state_snapshots (PRD T-DRAFT-25, Infra-owned).

rpc SavePositionStateSnapshots(SavePositionStateSnapshotsRequest) returns (SavePositionStateSnapshotsResponse)

GetPositionHistory

Retrieve the lifecycle event history for a single position.

rpc GetPositionHistory(GetPositionHistoryRequest) returns (GetPositionHistoryResponse)

UpdatePositionAttribution

Update lot-level attribution metadata (FIFO matching policy results) on an existing position event.

rpc UpdatePositionAttribution(UpdatePositionAttributionRequest) returns (UpdatePositionAttributionResponse)

SaveOutboxEntry

Enqueue a typed-event payload onto the accounting outbox for the async processor (VIB-3467).

rpc SaveOutboxEntry(SaveOutboxEntryRequest) returns (SaveOutboxEntryResponse)

GetOutboxEntry

Read a single outbox entry by id.

rpc GetOutboxEntry(GetOutboxEntryRequest) returns (GetOutboxEntryResponse)

GetOutboxPending

Fetch the next batch of pending outbox entries for the processor to drain.

rpc GetOutboxPending(GetOutboxPendingRequest) returns (GetOutboxPendingResponse)

UpdateOutboxEntry

Mark an outbox entry as processed, failed, or retry-pending.

rpc UpdateOutboxEntry(UpdateOutboxEntryRequest) returns (UpdateOutboxEntryResponse)

SaveLedgerAndRegistry

Atomic single-transaction commit of transaction_ledger, position_registry, and (when supplied) the position handle mapping. Replaces the legacy SaveLedgerEntrySavePositionEvent sequence with a single atomic write so a gateway crash between rows cannot orphan a registry handle or strand a phantom position (GH bug #2130).

Wire mode field (SaveLedgerAndRegistryRequest.mode):

Wire value Behavior
"" (proto3 default) Equivalent to "commit". Backwards-compatible for clients that don't set the field.
"commit" Full atomic three-write: ledger INSERT + registry UPSERT + handle backfill.
"registry_reconciliation" Registry UPSERT + handle backfill only — ledger is NOT touched. Used exclusively by PositionService.Reconcile when apply=true. Writing a synthesized ledger row on this path would pollute the immutable intent history (reconciliation discovers chain-only positions with no corresponding intent).

Any other value (including the framework-side CommitMode Literal values "accounting_only" and "registry") is rejected with INVALID_ARGUMENT. The Python framework wrapper (almanak/framework/accounting/commit.py:save_ledger_and_registry) exposes a higher-level CommitMode = Literal["accounting_only", "registry", "registry_reconciliation"] API where "accounting_only" is routed through SaveLedgerEntry instead and "registry" is translated to the wire's "commit".

Ticket: VIB-4197 (local SQLite) / VIB-4205 / T19 (hosted Postgres) / VIB-4210 / T24 (reconciliation mode). See Blueprint 28 §4.

rpc SaveLedgerAndRegistry(SaveLedgerAndRegistryRequest) returns (SaveLedgerAndRegistryResponse)

UpsertMigrationState

Insert or update the migration_state row for a (deployment_id, primitive, cutover_key) tuple. Drives the registry-mode cutover boot guard (Blueprint 06 §"Migration State Table"). Part of T22 / VIB-4208 (SQLite half).

rpc UpsertMigrationState(UpsertMigrationStateRequest) returns (UpsertMigrationStateResponse)

GetMigrationState

Read the current migration_state row for a (deployment_id, primitive, cutover_key). Returns null when no row exists — interpreted as "cutover not yet deployed for this surface" (raises RegistryCutoverNotDeployedError at boot if mode='registry' write is attempted).

rpc GetMigrationState(GetMigrationStateRequest) returns (GetMigrationStateResponse)

UpdateMigrationState

Partial-update of a migration_state row (e.g. recording backfill progress, watermarks). Distinct from MarkBackfillComplete which is the terminal one-shot flip.

rpc UpdateMigrationState(UpdateMigrationStateRequest) returns (UpdateMigrationStateResponse)

MarkBackfillComplete

One-shot atomic flip of the migration_state.complete flag from 0 to 1. Gates the boot guard against RegistryBackfillIncompleteError: until this RPC fires for a surface, registry-mode writes are refused in all execution modes (live, paper, dry_run).

rpc MarkBackfillComplete(MarkBackfillCompleteRequest) returns (MarkBackfillCompleteResponse)

GetPositionEventsFiltered

Read position_events rows filtered by (deployment_id, primitive, opened_after_block, status, …). Used by the cutover backfill job to project historical position lifecycle into position_registry. Distinct from GetPositionHistory which targets a single position lifecycle.

rpc GetPositionEventsFiltered(GetPositionEventsFilteredRequest) returns (GetPositionEventsFilteredResponse)

GetPositionRegistryOpenRows

Enumerate currently-open rows in position_registry for a (deployment_id, chain, primitive) tuple. Used by PositionService.Reconcile to compute the diff vs on-chain truth, and by the cutover backfill job to detect duplicates before insert.

rpc GetPositionRegistryOpenRows(GetPositionRegistryOpenRowsRequest) returns (GetPositionRegistryOpenRowsResponse)

ExecutionService

CompileIntent

Compile a strategy intent into an action bundle.

rpc CompileIntent(CompileIntentRequest) returns (CompilationResult)

Request:

message CompileIntentRequest {
  string intent_type = 1;          // Case-insensitive with aliases: "swap", "lp_open", etc.
  bytes intent_data = 2;           // JSON-serialized intent
  string chain = 3;
  string wallet_address = 4;
  map<string, string> price_map = 5;  // Token symbol -> USD price string (empty = use placeholder prices)
}

Response:

message CompilationResult {
  bool success = 1;
  bytes action_bundle = 2;     // JSON-serialized ActionBundle
  string error = 3;
  string error_code = 4;       // Structured error code
}

Execute

Execute an action bundle (sign, submit, confirm).

rpc Execute(ExecuteRequest) returns (ExecutionResult)

GetTransactionStatus

Get the status of a submitted transaction.

rpc GetTransactionStatus(TxStatusRequest) returns (TxStatus)

ObserveService

Log

Send log entries to the platform.

rpc Log(LogEntry) returns (Empty)

Alert

Send an alert to configured channels (Slack, Telegram).

rpc Alert(AlertRequest) returns (AlertResponse)

Request:

message AlertRequest {
  string severity = 1;     // info, warning, error, critical
  string title = 2;
  string message = 3;
  string deployment_id = 4;
}

RecordMetric

Record a custom metric.

rpc RecordMetric(MetricEntry) returns (Empty)

RecordTimelineEvent

Record a timeline event for a strategy (trades, rebalances, errors, state changes).

rpc RecordTimelineEvent(RecordTimelineEventRequest) returns (RecordTimelineEventResponse)

Request:

message RecordTimelineEventRequest {
  string deployment_id = 1;
  string event_type = 2;       // "TRADE", "REBALANCE", "ERROR", "STATE_CHANGE", etc.
  string description = 3;
  string tx_hash = 4;          // Optional: transaction hash
  string chain = 5;            // Optional: chain name
  string details_json = 6;     // Optional: JSON-encoded details
  int64 timestamp = 7;         // Optional: uses server time if 0
}

Response:

message RecordTimelineEventResponse {
  bool success = 1;
  string event_id = 2;
  string error = 3;
}

RpcService

Call

Make a single JSON-RPC call to a blockchain.

rpc Call(RpcRequest) returns (RpcResponse)

Request:

message RpcRequest {
  string chain = 1;        // Must be in allowed list
  string method = 2;       // Must be in allowed list
  string params = 3;       // JSON-encoded params
  string id = 4;
}

Response:

message RpcResponse {
  bool success = 1;
  string result = 2;       // JSON-encoded result
  string error = 3;        // JSON-encoded error
  string id = 4;
}

Allowed Chains:

EVM chains:

  • ethereum, arbitrum, base, optimism, polygon, avalanche, bsc, bnb, sonic, plasma, linea, blast, mantle, berachain, monad, xlayer, zerog, hyperevm, robinhood

Non-EVM chains:

  • solana

Allowed Methods (EVM): - eth_call - eth_getBalance - eth_getTransactionCount - eth_getTransactionReceipt - eth_getBlockByNumber - eth_getBlockByHash - eth_blockNumber - eth_chainId - eth_gasPrice - eth_estimateGas - eth_getLogs - eth_getCode - eth_getStorageAt - eth_sendRawTransaction - net_version

Allowed Methods (Solana): - getBalance - getTokenAccountsByOwner - getTokenAccountBalance - getTransaction - getSignaturesForAddress - getAccountInfo - getMultipleAccounts - getLatestBlockhash - getSlot - getBlockHeight - getEpochInfo - getMinimumBalanceForRentExemption - sendTransaction - simulateTransaction - getRecentPrioritizationFees - isBlockhashValid

Blocked Methods: - debug_* - Debugging methods - admin_* - Admin methods - personal_* - Personal key management - miner_* - Mining control - txpool_* - Transaction pool access

BatchCall

Make multiple JSON-RPC calls in parallel.

rpc BatchCall(RpcBatchRequest) returns (RpcBatchResponse)

Request:

message RpcBatchRequest {
  string chain = 1;
  repeated RpcRequest requests = 2;  // Max 100
}

QueryAllowance

Query ERC-20 token allowance (typed convenience method).

rpc QueryAllowance(AllowanceRequest) returns (AllowanceResponse)

Solana: Returns allowance = MAX_UINT64 and success = true. SPL tokens don't use ERC-20-style allowances.

QueryBalance

Query token balance (typed convenience method).

rpc QueryBalance(BalanceQueryRequest) returns (BalanceQueryResponse)

Solana: Returns an error directing callers to use MarketService.GetBalance() instead, which routes to the Solana-native balance provider.

QueryPositionLiquidity

Query LP position liquidity (typed convenience method).

rpc QueryPositionLiquidity(PositionLiquidityRequest) returns (PositionLiquidityResponse)

Solana: Returns "not applicable for Solana". Solana LP positions use different on-chain structures.

QueryPositionTokensOwed

Query tokens owed to an LP position (typed convenience method).

rpc QueryPositionTokensOwed(PositionTokensOwedRequest) returns (PositionTokensOwedResponse)

Solana: Returns "not applicable for Solana".

QueryV4PositionState

Read live Uniswap V4 LP position state on-chain for HIGH-confidence valuation (VIB-5024). Performs read-only eth_calls against connector-supplied addresses: PositionManager.getPositionLiquidity / getPoolAndPositionInfo, StateView.getSlot0, and — for V3-parity uncollected fees — StateView.getPositionInfo / getFeeGrowthInside. Returns success=False on any partial / inconsistent read so the valuer falls back to the ESTIMATED tier rather than ever emitting an incomplete HIGH value (the never-wrong-HIGH guarantee).

rpc QueryV4PositionState(V4PositionStateRequest) returns (V4PositionStateResponse)

Solana: Returns "not applicable for Solana".

IntegrationService

BinanceGetTicker

Get 24-hour ticker data from Binance.

rpc BinanceGetTicker(BinanceTickerRequest) returns (BinanceTickerResponse)

Request:

message BinanceTickerRequest {
  string symbol = 1;       // e.g., "BTCUSDT"
}

Response:

message BinanceTickerResponse {
  string symbol = 1;
  string price = 2;
  string price_change = 3;
  string price_change_percent = 4;
  string high_24h = 5;
  string low_24h = 6;
  string volume_24h = 7;
  string quote_volume_24h = 8;
  int64 timestamp = 9;
}

BinanceGetKlines

Get candlestick/kline data from Binance.

rpc BinanceGetKlines(BinanceKlinesRequest) returns (BinanceKlinesResponse)

Request:

message BinanceKlinesRequest {
  string symbol = 1;
  string interval = 2;     // 1m, 5m, 15m, 1h, 4h, 1d, etc.
  int32 limit = 3;         // Max 1000
  int64 start_time = 4;
  int64 end_time = 5;
}

BinanceGetOrderBook

Get order book depth from Binance.

rpc BinanceGetOrderBook(BinanceOrderBookRequest) returns (BinanceOrderBookResponse)

CoinGeckoGetPrice

Get token price from CoinGecko.

rpc CoinGeckoGetPrice(CoinGeckoGetPriceRequest) returns (CoinGeckoGetPriceResponse)

Request:

message CoinGeckoGetPriceRequest {
  string token_id = 1;     // e.g., "bitcoin", "ethereum"
  repeated string vs_currencies = 2;  // e.g., ["usd", "eur"]
}

CoinGeckoGetPrices

Get prices for multiple tokens.

rpc CoinGeckoGetPrices(CoinGeckoGetPricesRequest) returns (CoinGeckoGetPricesResponse)

CoinGeckoGetMarkets

Get market data with rankings.

rpc CoinGeckoGetMarkets(CoinGeckoGetMarketsRequest) returns (CoinGeckoGetMarketsResponse)

TheGraphQuery

Execute a GraphQL query on a subgraph.

rpc TheGraphQuery(TheGraphQueryRequest) returns (TheGraphQueryResponse)

Request:

message TheGraphQueryRequest {
  string subgraph_id = 1;  // e.g., "uniswap-v3-arbitrum"
  string query = 2;        // GraphQL query (max 10KB)
  string variables = 3;    // JSON-encoded variables
}

Note: Introspection queries (__schema, __type) are blocked.

CoinGeckoGetHistoricalPrice

Get historical price at a specific date.

rpc CoinGeckoGetHistoricalPrice(CoinGeckoHistoricalPriceRequest) returns (CoinGeckoHistoricalPriceResponse)

CoinGeckoGetMarketChartRange

Get price chart data for a date range.

rpc CoinGeckoGetMarketChartRange(CoinGeckoMarketChartRangeRequest) returns (CoinGeckoMarketChartRangeResponse)

CoinGeckoResolveContract

Resolve a chain-scoped contract address to its CoinGecko coin ID. The gateway owns the vendor request, API key, caching, and rate-limit handling; a configured backtest sidecar never falls back to direct CoinGecko HTTP for this lookup.

rpc CoinGeckoResolveContract(CoinGeckoResolveContractRequest) returns (CoinGeckoResolveContractResponse)

Request:

message CoinGeckoResolveContractRequest {
  string asset_platform = 1;   // CoinGecko platform ID, e.g. "base"
  string contract_address = 2; // EVM contract address
}

Response:

message CoinGeckoResolveContractResponse {
  string coin_id = 1; // Empty when found=false
  bool found = 2;     // False only for an explicit contract miss
  string source = 3;  // "coingecko_api" or "gateway_cache"
}

Gateway transport, authentication, rate-limit, and malformed-upstream failures are returned as typed gRPC errors rather than being reported as contract misses.

CoinGeckoGetOHLCV

Get CEX-reference OHLCV candles from CoinGecko (VIB-4847). Second CEX-capable provider in the OHLCV router's cex_primary failover chain. Candles are price-only (no volume); 1h/4h/1d timeframes only — sub-hour requests are below CoinGecko's 30m native floor.

rpc CoinGeckoGetOHLCV(CoinGeckoOHLCVRequest) returns (CoinGeckoOHLCVResponse)

Request:

message CoinGeckoOHLCVRequest {
  string token = 1;       // Token symbol (e.g., "WETH", "ARB")
  string timeframe = 2;   // Candle timeframe (1h, 4h, 1d)
  int32 limit = 3;        // Number of candles (most-recent N)
  string quote = 4;       // Quote currency (default "USD"; CoinGecko OHLC is fiat-quoted)
}

Response:

message CoinGeckoOHLCVCandle {
  int64 timestamp = 1;    // Unix timestamp in seconds (candle start)
  string open = 2;
  string high = 3;
  string low = 4;
  string close = 5;
  // No volume field: CoinGecko OHLC carries price-only candles.
}

message CoinGeckoOHLCVResponse {
  repeated CoinGeckoOHLCVCandle candles = 1;
}

CoinGeckoOnchainGetOHLCV

DEX OHLCV data from CoinGecko Onchain for on-chain pool pricing. Pool-level candles with volume — distinct from CoinGeckoGetOHLCV, which serves token-level, price-only CEX-reference candles.

rpc CoinGeckoOnchainGetOHLCV(CoinGeckoOnchainOHLCVRequest) returns (CoinGeckoOnchainOHLCVResponse)

GetWalletPortfolio

Get aggregated wallet portfolio valuation via Zerion.

rpc GetWalletPortfolio(WalletPortfolioRequest) returns (WalletPortfolioResponse)

GetWalletPositions

Get detailed wallet positions (DeFi protocol positions) via Zerion.

rpc GetWalletPositions(WalletPortfolioRequest) returns (WalletPortfolioResponse)

DashboardService

Provides data and actions for the operator dashboard.

ListStrategies

List strategies with optional filters.

rpc ListStrategies(ListStrategiesRequest) returns (ListStrategiesResponse)

Request:

message ListStrategiesRequest {
  string status_filter = 1;
  string chain_filter = 2;
  bool include_position = 3;
}

GetStrategyDetails

Get detailed information about a strategy including timeline and PnL history.

rpc GetStrategyDetails(GetStrategyDetailsRequest) returns (StrategyDetails)

Request:

message GetStrategyDetailsRequest {
  string deployment_id = 1;
  bool include_timeline = 2;
  bool include_pnl_history = 3;
  int32 timeline_limit = 4;
}

GetTimeline

Get timeline events for a strategy.

rpc GetTimeline(GetTimelineRequest) returns (GetTimelineResponse)

GetStrategyConfig

Get strategy configuration.

rpc GetStrategyConfig(GetStrategyConfigRequest) returns (StrategyConfigResponse)

GetStrategyState

Get strategy state.

rpc GetStrategyState(GetStrategyStateRequest) returns (StrategyStateResponse)

ExecuteAction

Execute an operator action on a strategy.

rpc ExecuteAction(ExecuteActionRequest) returns (ExecuteActionResponse)

Request:

message ExecuteActionRequest {
  string deployment_id = 1;
  string action = 2;       // PAUSE, RESUME, BUMP_GAS, CANCEL_TX, EMERGENCY_UNWIND
  string reason = 3;
  map<string, string> params = 4;
}

RegisterStrategyInstance

Register a new strategy instance in the persistent registry.

rpc RegisterStrategyInstance(RegisterInstanceRequest) returns (RegisterInstanceResponse)

UpdateStrategyInstanceStatus

Update the status of a registered strategy instance.

rpc UpdateStrategyInstanceStatus(UpdateInstanceStatusRequest) returns (UpdateInstanceStatusResponse)

ArchiveStrategyInstance

Archive a strategy instance (soft delete).

rpc ArchiveStrategyInstance(ArchiveInstanceRequest) returns (ArchiveInstanceResponse)

PurgeStrategyInstance

Permanently remove a strategy instance from the registry.

rpc PurgeStrategyInstance(PurgeInstanceRequest) returns (PurgeInstanceResponse)

GetTransactionLedger

Retrieve the transaction ledger for a strategy instance.

rpc GetTransactionLedger(GetTransactionLedgerRequest) returns (GetTransactionLedgerResponse)

GetPnLSummary

Aggregated PnL summary (realized, unrealized, lifetime) for the operator dashboard. Sourced from portfolio_metrics and accounting_events.

rpc GetPnLSummary(GetPnLSummaryRequest) returns (PnLSummary)

GetCostStack

Per-strategy cost decomposition (gas, slippage, protocol fees, MEV).

rpc GetCostStack(GetCostStackRequest) returns (CostStackInfo)

GetAuditPosture

Audit-readiness snapshot: lot-policy version coverage, missing receipts, outbox lag, and reconciliation drift signals.

rpc GetAuditPosture(GetAuditPostureRequest) returns (AuditPosture)

GetTradeTape

Time-ordered tape of executed trades for the dashboard timeline view.

rpc GetTradeTape(GetTradeTapeRequest) returns (GetTradeTapeResponse)

GetActivityFeed

Time-ordered feed of strategy lifecycle events (intent emitted, compiled, executed, teardown, alerts) for the dashboard activity view. Distinct from GetTradeTape which shows only trade fills.

rpc GetActivityFeed(GetActivityFeedRequest) returns (GetActivityFeedResponse)

GetPositions

Registry-authoritative position identity (position_registry) joined with snapshot-authoritative valuation (portfolio_snapshots + position_state_snapshots). Replaces SQLite-direct reads in framework/dashboard/pages/detail.py. Carries cutover_state per accounting_category so renderers can split authoritative positions from pre-cutover "Unverified Holdings". (VIB-4493)

rpc GetPositions(GetPositionsRequest) returns (GetPositionsResponse)

GetPositionRangeHistory

Per-position range / fee / balance history. Source-routes by primitive: LP/PERP from position_events, lending from accounting_events. Swap/prediction return empty + stub_message (history concept N/A). (VIB-4493)

rpc GetPositionRangeHistory(GetPositionRangeHistoryRequest) returns (GetPositionRangeHistoryResponse)

GetReconciliationReport

Three-way diff across transaction_ledger / portfolio_snapshots / position_registry. Read-only. LP-only in v1; non-LP primitives surface per-primitive stubs (pending VIB-4202/4209/4501). 5s TTL cache. (VIB-4493)

rpc GetReconciliationReport(GetReconciliationReportRequest) returns (GetReconciliationReportResponse)

PreviewReconcile

Dry-run reconciliation. Thin wrapper over PositionService.Reconcile(apply=false). Returns a preview_token bound to current registry/ledger state hashes — pass to ApplyReconcile to apply. Operator-only; requires the x-operator-token second-factor header when ALMANAK_GATEWAY_OPERATOR_TOKEN is set. (VIB-4493)

rpc PreviewReconcile(PreviewReconcileRequest) returns (PreviewReconcileResponse)

ApplyReconcile

Applies a previously-issued preview. Fails with STATE_DRIFT if registry/ledger state changed since the preview was issued. Operator-only; requires the x-operator-token second-factor header when ALMANAK_GATEWAY_OPERATOR_TOKEN is set. (VIB-4493)

rpc ApplyReconcile(ApplyReconcileRequest) returns (ApplyReconcileResponse)

RefreshRegistryFromChain

Forces fresh on-chain reads for every position in position_registry for the strategy. Updates on_chain_verified_at, re-emits divergent events. Rate-limited at the DashboardService layer to one in-flight per strategy. Operator-only; requires the x-operator-token second-factor header when ALMANAK_GATEWAY_OPERATOR_TOKEN is set. (VIB-4493)

rpc RefreshRegistryFromChain(RefreshRegistryFromChainRequest) returns (RefreshRegistryFromChainResponse)

FundingRateService

Provides perpetual funding rate data from venues like GMX V2 and Hyperliquid.

GetFundingRate

Get current funding rate for a market on a specific venue.

rpc GetFundingRate(FundingRateRequest) returns (FundingRateResponse)

Request:

message FundingRateRequest {
  string venue = 1;        // gmx_v2, hyperliquid
  string market = 2;       // e.g., ETH-USD, BTC-USD
  string chain = 3;
}

Response:

message FundingRateResponse {
  string venue = 1;
  string market = 2;
  string rate_hourly = 3;
  string rate_8h = 4;
  string rate_annualized = 5;
  int64 next_funding_time = 6;
  string open_interest_long = 7;
  string open_interest_short = 8;
  string mark_price = 9;
  string index_price = 10;
  bool is_live_data = 11;
  bool success = 12;
  string error = 13;
}

GetFundingRateSpread

Get the funding rate spread between two venues.

rpc GetFundingRateSpread(FundingRateSpreadRequest) returns (FundingRateSpreadResponse)

Request:

message FundingRateSpreadRequest {
  string market = 1;
  string venue_a = 2;
  string venue_b = 3;
  string chain = 4;
}

SimulationService

Simulate transaction bundles before execution using Tenderly or Alchemy.

SimulateBundle

rpc SimulateBundle(SimulateBundleRequest) returns (SimulateBundleResponse)

Request:

message SimulateBundleRequest {
  string chain = 1;
  repeated SimulateTransaction transactions = 2;
  repeated SimulateStateOverride state_overrides = 3;
  string simulator = 4;   // "tenderly", "alchemy", or empty for auto-select
}

Response:

message SimulateBundleResponse {
  bool success = 1;
  bool simulated = 2;
  repeated int64 gas_estimates = 3;
  string revert_reason = 4;
  repeated string warnings = 5;
  string simulation_url = 6;
  string simulator_used = 7;
  string error = 8;
}

PoolAnalyticsService

Aggregated DEX pool analytics (TVL, volume, fees) sourced through the gateway's analytics providers. Used by risk-adjusted strategies that gate position size on pool depth or 24h turnover.

GetPoolAnalytics

rpc GetPoolAnalytics(PoolAnalyticsRequest) returns (PoolAnalyticsResponse)

ListTokenPools

List pools where a token is base or quote, with product-distinct dex ids sourced from CoinGecko Onchain (aerodrome-base vs aerodrome-slipstream*, where aggregator labels cannot distinguish the products). Serves symbolic pool resolution for product-ambiguous venue families (Aerodrome/Velodrome classic vs Slipstream) in the backtest LP adapter.

rpc ListTokenPools(TokenPoolsRequest) returns (TokenPoolsResponse)

Request:

message TokenPoolsRequest {
  string chain = 1;          // Required. Chain name (canonical or registered alias).
  string token_address = 2;  // Required. Token address (EVM case-insensitive; Solana case-sensitive).
  int32  page = 3;           // 0 (default) = ATOMIC bounded fetch (gateway pages upstream on
                             // raw row counts, one consistent snapshot, `complete` set).
                             // >=1 = single upstream page (compat/diagnostic).
}

Response:

message TokenPoolsResponse {
  string chain = 1;          // Echoed-back, canonicalized.
  string token_address = 2;  // Echoed-back, chain-normalized.
  repeated TokenPoolRow pools = 3;
  string source = 4;         // "coingecko_onchain"
  int64  observed_at = 5;    // Unix seconds when the gateway fetched the data.
  bool   success = 6;
  string error = 7;
  bool   complete = 8;       // True when the set is complete per upstream RAW row
                             // counts; false when the page bound was hit with more
                             // upstream data. Consumers select by RANK-ORDER within
                             // the returned window (first exact match = the canonical
                             // pool) — never a deepest-of-all claim over a truncated
                             // window; a pair with no in-window match fails closed.
}

Each TokenPoolRow carries the pool address (chain-normalized and syntactically validated — non-address identities are dropped), the product-distinct dex_id, the pool name, reserve_usd as decimal-as-string (Empty ≠ Zero: absent or non-finite readings are "", never "NaN"), and the base/quote token addresses (chain-normalized + validated; "" when omitted or invalid). Identity handling is chain-aware throughout: EVM addresses lower-case, Solana-family base58 preserves case. Atomic fetches page the upstream on raw row counts (a sanitized junk row never truncates the search) up to a 5-page bound. Results are TTL-cached gateway-side atomically per snapshot (60s positive, 30s negative; the cache is bounded and swept), with single-flight deduplication of concurrent identical misses. Errors follow the dual-channel envelope: provider/schema failures return structured UNAVAILABLE, never UNKNOWN.

PoolHistoryService

Historical pool snapshots — TVL, volume, fee revenue, and per-token reserves over time at 1h / 4h / 1d resolution.

The service is disabled by default (pool_history_enabled=False in GatewaySettings): the hosted rollout enables it via ALMANAK_GATEWAY_POOL_HISTORY_ENABLED=true as deployment config once provider egress and keys are provisioned (VIB-4730 + VIB-4863). When the flag is off the handler returns UNAVAILABLE with a message pointing at VIB-4728.

GetPoolHistory

Return a time-series of PoolSnapshot rows for a single pool. Provider fallback order is dispatcher policy (VIB-4753 / POOL-5):

  • 1h / 4h: TheGraph → CoinGecko Onchain (DefiLlama is skipped — daily-only).
  • 1d: TheGraph → DefiLlama → CoinGecko Onchain.

Returns UNAVAILABLE when all eligible providers fail or when the pool is not found anywhere — never fake-success with an empty snapshots array.

rpc GetPoolHistory(PoolHistoryRequest) returns (PoolHistoryResponse)

Request:

message PoolHistoryRequest {
  string pool_address = 1;       // EVM: case-insensitive; Solana: case-sensitive base58
  string chain = 2;              // e.g. "arbitrum", "ethereum", "base"
  string protocol = 3;           // e.g. "uniswap_v3", "aerodrome"
  int64 start_ts = 4;            // Unix seconds, UTC
  int64 end_ts = 5;              // Unix seconds, UTC (rejected if in the future)
  Resolution resolution = 6;     // RESOLUTION_1H | RESOLUTION_4H | RESOLUTION_1D
}

Response:

message PoolHistoryResponse {
  repeated PoolSnapshot snapshots = 1;     // Ascending by timestamp, one row per aligned bucket
  TruncationReason truncation_reason = 2;  // CAP_EXCEEDED | PROVIDER_PAGE_CAP | PROVIDER_RETENTION
  int64 next_start_ts = 3;                 // Re-chunk hint for paged backfill
  string source = 4;                       // "the_graph" | "defillama" | "coingecko_onchain"
  bool finalized_only = 5;                 // false if any row is provisional within the finality cutoff
  bool success = 6;
  string error = 7;
}

Soft caps are operator-tunable via ALMANAK_GATEWAY_POOL_HISTORY_MAX_DAYS_{1H,4H,1D} (defaults: 90, 180, 730 days). When a request exceeds a cap the handler returns a CAP_EXCEEDED truncation and a next_start_ts hint for the next chunk.

RateHistoryService

VIB-4859 / W7 of epic VIB-4851. Lending APY (live + historical), perpetual funding-rate history, DEX TWAP (single observation + series), and DEX trading volume history. Replaces the strategy-side HTTP / GraphQL / Web3 egress that used to live in framework/data/rates/{monitor,history}.py and framework/backtesting/pnl/providers/{lending_apy,twap,dex/*} — all of which violated the gateway-boundary rule. After W7, those framework readers are thin gRPC clients of this service.

Dispatcher pattern mirrors FundingRateService / PoolAnalyticsService: the servicer resolves (protocol, venue, dex) → capability provider at construction time (O(1) lookup per request), validates the request shape, and delegates to the connector. Connectors implement GatewayLendingRateHistoryCapability / GatewayFundingHistoryCapability / GatewayPerpPriceHistoryCapability / GatewayDexTwapCapability / GatewayDexVolumeCapability. Adding a new implementer is a connector-folder change with zero gateway-service edits.

Error semantics — dual-channel wire shape:

  • status OK + success=true → fresh data in the envelope.
  • status OK + success=false → framework raises DataSourceUnavailable (no silent zero-fill, no default-rate fallback).
  • non-OK status (INVALID_ARGUMENT / UNAVAILABLE / …) → framework raises DataSourceUnavailable; __cause__ preserves the grpc.RpcError.

GetLendingRateCurrent

Single-point live lending rate (supply or borrow APY + utilisation) via the connector's on-chain path (e.g. Aave V3's AaveProtocolDataProvider.getReserveData(asset)).

rpc GetLendingRateCurrent(GetLendingRateCurrentRequest) returns (LendingRatePointResponse)

Request:

message GetLendingRateCurrentRequest {
  string protocol = 1;       // e.g. "aave_v3", "compound_v3", "morpho_blue"
  string chain = 2;          // e.g. "ethereum", "arbitrum"
  string asset_symbol = 3;   // e.g. "USDC", "WETH"
  string side = 4;           // "supply" or "borrow"
}

Response: LendingRatePointResponse { protocol, chain, asset_symbol, side, point: LendingRatePoint, source, is_live_data, success, error }.

GetLendingRateHistory

Time-series of lending APY / utilisation over [start_ts, end_ts). Migrated body of framework/data/rates/history.py (TheGraph crawl with DefiLlama fallback).

rpc GetLendingRateHistory(GetLendingRateHistoryRequest) returns (LendingRateHistoryResponse)

GetFundingRateHistory

Historical funding-rate series for a perp (venue, market). Sibling of FundingRateService.GetFundingRate (which serves the live rate only).

rpc GetFundingRateHistory(GetFundingRateHistoryRequest) returns (FundingRateHistoryResponse)

GetPerpPriceCandles

Return one bounded page of venue-native perpetual index-price candles. The gateway resolves the caller's market label/address through the connector's live catalogue and verifies its address tuple on-chain before deriving the vendor symbol. The request timeframe is exact; the gateway never substitutes a coarser value. Backtest auto negotiation is framework-side and probes this RPC finest-to-coarsest for complete coverage.

rpc GetPerpPriceCandles(GetPerpPriceCandlesRequest) returns (PerpPriceCandlePageResponse)

Every successful page repeats market_token, index_token, index_symbol, and timeframe so pagination can reject identity drift. OHLC decimals are strings. Volume is intentionally absent: this is the index/mark price plane, not venue execution volume.

GetDexTwap

Single TWAP observation for a Uniswap-V3-style pool (or a V3 fork with the same observe(secondsAgos) ABI). secs_ago_start is the older boundary, secs_ago_end is the newer boundary — matches the on-chain observe(secondsAgos) convention. Pass as_of_block > 0 to pin the observation to an archive block; leave as_of_block = 0 for current head.

rpc GetDexTwap(GetDexTwapRequest) returns (DexTwapPointResponse)

GetDexTwapSeries

TWAP series at interval_secs spacing over [start_ts, end_ts).

rpc GetDexTwapSeries(GetDexTwapSeriesRequest) returns (DexTwapHistoryResponse)

GetDexPoolStateSeries

Historical exact-address pool state at interval_secs spacing over the inclusive [start_ts, end_ts] grid. The gateway resolves each grid timestamp to the latest block at-or-before it and reads that pool's slot0, liquidity, fee, immutable token identities/decimals, and token balances from archive state. It never discovers a substitute pool or derives state from token/USD prices.

rpc GetDexPoolStateSeries(GetDexPoolStateSeriesRequest) returns (DexPoolStateHistoryResponse)

message GetDexPoolStateSeriesRequest {
  string dex = 1;
  string chain = 2;
  string pool_address = 3;
  int64 start_ts = 4;
  int64 end_ts = 5;
  int64 interval_secs = 6;
}

message DexPoolStatePoint {
  int64 timestamp = 1;
  int64 block_number = 2;
  string sqrt_price_x96 = 3;
  int64 tick = 4;
  string liquidity = 5;
  string token0 = 6;
  string token1 = 7;
  int32 token0_decimals = 8;
  int32 token1_decimals = 9;
  int32 fee_tier = 10;
  string reserve0_raw = 11;
  string reserve1_raw = 12;
}

GetDexLwap

Liquidity-weighted spot price across the supplied Uniswap-V3-style pools (LWAP = Σ(price·liquidity) / Σ(liquidity) over each pool's pool-native token1/token0 spot). The framework resolves the candidate pool addresses; the gateway reads slot0() + liquidity() + token0/token1 per pool server-side. When base_token / quote_token addresses are supplied, pools not containing exactly that pair are dropped before weighting, so a stale or foreign-pair pool address cannot poison the aggregate. Fails closed (success=false) when no pool is readable, the requested pair matches nothing, or total in-range liquidity is zero — it never emits an empty or fabricated price. The caller (MarketSnapshot.lwap) re-orients the returned native price to the requested quote/base (VIB-4948 / ALM-2770).

rpc GetDexLwap(GetDexLwapRequest) returns (DexLwapPointResponse)

GetDexVolumeHistory

Historical trading-volume series for a DEX pool (USD volume at the requested bucket spacing).

rpc GetDexVolumeHistory(GetDexVolumeHistoryRequest) returns (DexVolumeHistoryResponse)

GetGasPriceAt

Current or historical gas-price observation for an EVM chain. timestamp = 0 uses the gateway-owned explorer gas oracle. timestamp > 0 reads baseFeePerGas from the gateway's archive RPC path; the returned GasPricePoint.timestamp is the actual observed block timestamp, not the requested timestamp.

rpc GetGasPriceAt(GetGasPriceAtRequest) returns (GasPricePointResponse)

Request:

message GetGasPriceAtRequest {
  string chain = 1;    // EVM chain name, e.g. "ethereum", "arbitrum", "base"
  int64 timestamp = 2; // Unix seconds; 0 means current gas oracle
}

Response: GasPricePointResponse { chain, point: GasPricePoint, source, success, error }.

GetOraclePrice

Return the current observation from one explicitly requested oracle provider. This is provider-exact data rather than MarketService.GetPrice's aggregated price policy, so freshness and divergence checks preserve their provenance.

rpc GetOraclePrice(GetOraclePriceRequest) returns (OraclePriceResponse)

Request: GetOraclePriceRequest { provider, chain, token }.

Response: OraclePriceResponse { provider, chain, token, point: OraclePricePoint, success, error }. OraclePricePoint.observation_id is the provider-native identifier; Chainlink returns its uint80 round ID as a decimal string.

GetOraclePriceHistory

Return a bounded provider-exact oracle series over the half-open interval [start_ts, end_ts). The caller must supply max_points in [1, 10000]; the gateway also rejects windows longer than 366 days. Provider-specific archive RPC selection, decoding, and historical traversal remain gateway-owned.

rpc GetOraclePriceHistory(GetOraclePriceHistoryRequest) returns (OraclePriceHistoryResponse)

Request: GetOraclePriceHistoryRequest { provider, chain, token, start_ts, end_ts, max_points }.

Response: OraclePriceHistoryResponse { provider, chain, token, points: OraclePricePoint[], success, error }.

Wire conventions — Decimal fields are encoded as strings (mirrors PoolHistoryService / FundingRateService). Empty string means "unmeasured by this provider for this row"; the framework boundary maps it to Python None — never Decimal("0"). Per-side selection on lending (e.g. side="supply") returns the borrow field as the empty string, NOT zero.

PolymarketService

Proxy for the Polymarket CLOB API. Provides market data, order management, and position tracking.

GetMarket

Fetch metadata for a single Polymarket market (tokens, condition id, status).

rpc GetMarket(PolymarketGetMarketRequest) returns (PolymarketMarketResponse)

GetMarkets

List markets with optional filters (category, status, cursor pagination).

rpc GetMarkets(PolymarketGetMarketsRequest) returns (PolymarketMarketsResponse)

GetMidpoint

Return the current midpoint price for a token (CLOB outcome).

rpc GetMidpoint(PolymarketMidpointRequest) returns (PolymarketMidpointResponse)

GetPrice

Return the current best bid or ask price for a token, depending on the requested side.

rpc GetPrice(PolymarketPriceRequest) returns (PolymarketPriceResponse)

GetSimplifiedMarkets

List condensed market summaries optimized for discovery flows.

rpc GetSimplifiedMarkets(PolymarketGetSimplifiedMarketsRequest) returns (PolymarketSimplifiedMarketsResponse)

GetOrderBook

Return the full bid / ask book for a token.

rpc GetOrderBook(PolymarketOrderBookRequest) returns (PolymarketOrderBookResponse)

GetTickSize

Return the minimum price increment (tick size) accepted for a token.

rpc GetTickSize(PolymarketTickSizeRequest) returns (PolymarketTickSizeResponse)

GetSpread

Return the current bid-ask spread for a token.

rpc GetSpread(PolymarketSpreadRequest) returns (PolymarketSpreadResponse)

GetPriceHistory

Return time-bucketed historical prices for a token (used for backtesting and charts).

rpc GetPriceHistory(PolymarketGetPriceHistoryRequest) returns (PolymarketPriceHistoryResponse)

GetTradeTape

Return a time-ordered trade tape across markets (most-recent first).

rpc GetTradeTape(PolymarketGetTradeTapeRequest) returns (PolymarketTradeTapeResponse)

GetBalanceAllowance

Return the funder's USDC balance and CLOB exchange allowance.

rpc GetBalanceAllowance(PolymarketBalanceAllowanceRequest) returns (PolymarketBalanceAllowanceResponse)

GetOpenOrders

List open orders for the configured funder, optionally filtered by market.

rpc GetOpenOrders(PolymarketGetOpenOrdersRequest) returns (PolymarketOpenOrdersResponse)

GetOrder

Return details for a single order by id.

rpc GetOrder(PolymarketGetOrderRequest) returns (PolymarketOrderInfoResponse)

GetTradesHistory

List historical fills for the funder, with cursor pagination.

rpc GetTradesHistory(PolymarketGetTradesRequest) returns (PolymarketTradesResponse)

GetPositions

List the funder's current Polymarket outcome-token positions.

rpc GetPositions(PolymarketGetPositionsRequest) returns (PolymarketPositionsResponse)

CancelOrder

Cancel a single open order by id.

rpc CancelOrder(PolymarketCancelOrderRequest) returns (PolymarketCancelResponse)

CancelOrders

Cancel multiple open orders in a single call.

rpc CancelOrders(PolymarketCancelOrdersRequest) returns (PolymarketCancelResponse)

CancelAll

Cancel every open order for the funder.

rpc CancelAll(PolymarketCancelAllRequest) returns (PolymarketCancelResponse)

CreateAndPostOrder

Sign a limit order against the funder's key and POST it to the CLOB in a single RPC. Returns the order id and CLOB acknowledgement payload.

rpc CreateAndPostOrder(PolymarketCreateOrderRequest) returns (PolymarketOrderResponse)

CreateAndPostMarketOrder

Sign and submit a market order in a single RPC (fills against the resting book).

rpc CreateAndPostMarketOrder(PolymarketMarketOrderRequest) returns (PolymarketOrderResponse)

EnsoService

Proxy for Enso Finance routing and bundling, supporting cross-chain swaps.

GetRoute

Get an optimized swap route.

rpc GetRoute(EnsoRouteRequest) returns (EnsoRouteResponse)

Request:

message EnsoRouteRequest {
  string chain = 1;
  string token_in = 2;
  string token_out = 3;
  string amount_in = 4;
  string from_address = 5;
  string receiver = 6;
  int32 slippage_bps = 7;
  string routing_strategy = 8;
  int32 max_price_impact_bps = 9;
  int32 destination_chain_id = 10;
  string refund_receiver = 11;
}

GetQuote

Get a price quote without generating calldata.

rpc GetQuote(EnsoQuoteRequest) returns (EnsoQuoteResponse)

GetApproval

Get the approval transaction for a token.

rpc GetApproval(EnsoApprovalRequest) returns (EnsoApprovalResponse)

GetBundle

Bundle multiple DeFi actions into a single transaction.

rpc GetBundle(EnsoBundleRequest) returns (EnsoBundleResponse)

TokenService

Provides token resolution and on-chain metadata lookups.

ResolveToken

Resolve a token by symbol or address to get its full metadata.

rpc ResolveToken(ResolveTokenRequest) returns (TokenMetadataResponse)

GetTokenMetadata

Get metadata for a token by address.

rpc GetTokenMetadata(GetTokenMetadataRequest) returns (TokenMetadataResponse)

GetTokenDecimals

Get the decimal precision for a token.

rpc GetTokenDecimals(GetTokenDecimalsRequest) returns (GetTokenDecimalsResponse)

BatchResolveTokens

Resolve multiple tokens in a single call.

rpc BatchResolveTokens(BatchResolveTokensRequest) returns (BatchResolveTokensResponse)

LifecycleService

Agent state management and command dispatch for V2 deployments.

WriteState

Write the current agent state (INITIALIZING, RUNNING, PAUSED, ERROR, STOPPING, TERMINATED).

rpc WriteState(WriteAgentStateRequest) returns (WriteAgentStateResponse)

ReadState

Read the current agent state.

rpc ReadState(ReadAgentStateRequest) returns (ReadAgentStateResponse)

Heartbeat

Send a heartbeat to update the last activity timestamp and increment the iteration count.

rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse)

ReadCommand

Read the most recent unprocessed command for an agent (PAUSE, RESUME, STOP).

rpc ReadCommand(ReadAgentCommandRequest) returns (ReadAgentCommandResponse)

AckCommand

Acknowledge (mark processed) a command.

rpc AckCommand(AckAgentCommandRequest) returns (AckAgentCommandResponse)

WriteCommand

Write a command to an agent.

rpc WriteCommand(WriteAgentCommandRequest) returns (WriteAgentCommandResponse)

TeardownService

Hosted teardown state routing for V2 deployments. Splits into two halves: the request half (teardown_requests) which tracks operator-issued teardown signals through their lifecycle (acknowledged → started → progress → completed / failed / cancelled), and the adapter half (teardown_execution_state + teardown_approvals) which persists the in-flight intent state machine and operator approvals required for risk-elevated teardown steps. See docs/internal/blueprints/14-teardown-system.md.

CreateTeardownRequest

Insert a new operator-issued teardown request. This is the row that almanak strat teardown request -s <deployment_id> writes; the runner's polling query picks it up by deployment_id.

rpc CreateTeardownRequest(CreateTeardownRequestRequest) returns (CreateTeardownRequestResponse)

GetTeardownRequest

Fetch a single teardown request by id.

rpc GetTeardownRequest(GetTeardownRequestRequest) returns (GetTeardownRequestResponse)

GetActiveTeardownRequest

Fetch the active (non-terminal) teardown request for a strategy, if any.

rpc GetActiveTeardownRequest(GetActiveTeardownRequestRequest) returns (GetTeardownRequestResponse)

GetPendingTeardownRequests

List teardown requests not yet acknowledged by any runner (used by the runner polling loop and the operator dashboard's pending queue).

rpc GetPendingTeardownRequests(Empty) returns (ListTeardownRequestsResponse)

GetAllActiveTeardownRequests

List every non-terminal teardown request across all strategies (operator dashboard).

rpc GetAllActiveTeardownRequests(Empty) returns (ListTeardownRequestsResponse)

GetAllTeardownRequests

List every teardown request (administrative / audit endpoint).

rpc GetAllTeardownRequests(Empty) returns (ListTeardownRequestsResponse)

UpdateTeardownRequest

Generic update of a teardown request (mode, slippage tolerance, operator note).

rpc UpdateTeardownRequest(UpdateTeardownRequestRequest) returns (TeardownRequestMutationResponse)

AcknowledgeTeardownRequest

Mark a request as acknowledged by the runner that owns the strategy. Until this fires, the request is in pending.

rpc AcknowledgeTeardownRequest(AckTeardownRequestRequest) returns (TeardownRequestMutationResponse)

MarkTeardownStarted

Transition the request to started once the teardown manager begins executing intents (this is the state almanak strat teardown request --wait reports).

rpc MarkTeardownStarted(MarkTeardownStartedRequest) returns (TeardownRequestMutationResponse)

UpdateTeardownProgress

Stream per-step progress (current intent index, status, last tx_hash). Read by teardown request --wait to print the live progress feed.

rpc UpdateTeardownProgress(UpdateTeardownProgressRequest) returns (TeardownRequestMutationResponse)

MarkTeardownCompleted

Terminal success state — every teardown intent completed and the strategy is fully unwound.

rpc MarkTeardownCompleted(MarkTeardownCompletedRequest) returns (TeardownRequestMutationResponse)

MarkTeardownFailed

Terminal failure state with an error code + message. The on-chain state may be partially unwound; check the latest transaction_ledger rows for the strategy.

rpc MarkTeardownFailed(MarkTeardownFailedRequest) returns (TeardownRequestMutationResponse)

RequestTeardownCancel

Operator-issued cancellation signal (cooperative — the runner stops at the next safe checkpoint).

rpc RequestTeardownCancel(RequestTeardownCancelRequest) returns (BoolMutationResponse)

MarkTeardownCancelled

Terminal cancellation state — the runner halted teardown at a safe checkpoint.

rpc MarkTeardownCancelled(MarkTeardownCancelledRequest) returns (TeardownRequestMutationResponse)

DeleteTeardownRequest

Hard-delete a request row (administrative endpoint).

rpc DeleteTeardownRequest(DeleteTeardownRequestRequest) returns (BoolMutationResponse)

SaveTeardownState

Persist a snapshot of the teardown intent state machine. Used by the runner to survive restarts mid-teardown.

rpc SaveTeardownState(SaveTeardownStateRequest) returns (SaveTeardownStateResponse)

LoadTeardownState

Restore the teardown state machine on runner restart.

rpc LoadTeardownState(LoadTeardownStateRequest) returns (LoadTeardownStateResponse)

DeleteTeardownState

Clear persisted teardown state after a terminal transition.

rpc DeleteTeardownState(DeleteTeardownStateRequest) returns (DeleteTeardownStateResponse)

CreateApprovalRequest

Block teardown on operator approval for risk-elevated steps (e.g., HARD-mode slippage bumps). The runner waits on a matching GetApprovalResponse / WriteApprovalResponse pair.

rpc CreateApprovalRequest(CreateApprovalRequestRequest) returns (CreateApprovalRequestResponse)

GetApprovalResponse

Poll for an operator's response to an approval request by approval id.

rpc GetApprovalResponse(GetApprovalResponseRequest) returns (GetApprovalResponseResponse)

WriteApprovalResponse

Operator writes an approve / deny decision against an approval id.

rpc WriteApprovalResponse(WriteApprovalResponseRequest) returns (BoolMutationResponse)

GetLatestPendingApproval

Fetch the most recent pending approval for a strategy (operator UI lookup when approval id is not known).

rpc GetLatestPendingApproval(GetLatestPendingApprovalRequest) returns (GetLatestPendingApprovalResponse)

WriteApprovalResponseByStrategy

Approve / deny by strategy id (used when the operator UI does not yet have the approval id but has the strategy context).

rpc WriteApprovalResponseByStrategy(WriteApprovalResponseByStrategyRequest) returns (BoolMutationResponse)

PositionService

Control-plane reconciliation of position_registry against on-chain truth. v1 scope is UniV3 LP only (T24 / VIB-4210). Backs the almanak ax positions reconcile operator CLI. Closes user-facing bug GH #2131 (phantom-missing rows after partial-write outages).

Reconcile

Re-derive registry rows for a deployment by querying chain state and diffing against the current position_registry contents. Reports four diff categories:

  • matched — on-chain and registry agree.
  • phantom_missing — on-chain has a position, registry doesn't (the GH #2131 case).
  • stranded — registry status='open', chain absent. Never auto-closed — operator must run a teardown for the position.
  • rebuilt — phantom-missing rows just written (only when request apply=true).

When apply=false (default), the RPC is dry-run and returns the diff without writing. When apply=true, phantom-missing rows are inserted into position_registry via SaveLedgerAndRegistry(mode='registry_reconciliation') (registry-only write; ledger is NEVER touched on the reconciliation path).

rpc Reconcile(ReconcileRequest) returns (ReconcileResponse)

Request:

message ReconcileRequest {
  string deployment_id = 1;             // ClassName:hash
  string chain = 2;
  string wallet_address = 3;
  repeated string primitives = 4;        // v1: "lp" only
  repeated string physical_identity_hashes = 5;  // optional filter
  bool apply = 6;                        // default false (dry-run)
  int64 max_age_blocks = 7;              // 0 = no check; v1: rejected if > 0 on first-page requests (no page_cursor)
  bytes page_cursor = 8;                 // Opaque pagination cursor (v1: honored only for stale-cursor validation; single-page contract)
  int32 page_size = 9;                   // Max rows per page (default 64, cap 256; v1: clamped but does not slice)
  string operator_note = 10;             // ≤ 256 bytes
  string trigger = 11;                   // operator_cli | hosted_boot | dashboard | ci
}

PerpFillService

Executed fills and funding settlements for a wallet on a perp venue (VIB-5595). Backs the perp accounting path: taker fees are routed via ProtocolFees.perp_fee_usd, and realized PnL / funding via PerpAccountingEvent. HTTP egress to the venue (e.g. the Hyperliquid Info API userFills / userFunding) happens server-side only; the framework reader is a thin gRPC client. Empty≠Zero: a venue field that is not reported stays unmeasured (empty string) and is never coerced to "0".

GetUserFills

Executed fills for a wallet, optionally filtered to a coin and a start time.

rpc GetUserFills(UserFillsRequest) returns (UserFillsResponse)

Request:

message UserFillsRequest {
  string venue = 1;            // one per perp connector; unknown -> INVALID_ARGUMENT
  string wallet_address = 2;   // account whose fills to read (EVM address)
  string coin = 3;             // optional coin filter (e.g. "BTC"). Empty = all
  int64 start_time_ms = 4;     // optional lower bound, epoch ms. 0 = no bound
}

Response:

message PerpFill {
  string coin = 1;             // market symbol (e.g. "BTC")
  string px = 2;               // fill price (decimal string; empty = unreported)
  string sz = 3;               // fill size, base asset absolute (empty = unmeasured)
  string dir = 4;              // venue direction label ("Open Long", ...)
  string fee = 5;              // fee paid, USD (empty = unmeasured, never "0")
  string closed_pnl = 6;       // realized PnL on closing fills (empty = unmeasured)
  string oid = 7;              // venue order id
  string cloid = 8;            // client order id (correlation key to the intent)
  int64 time_ms = 9;           // fill time, epoch ms
  bool crossed = 10;           // true iff taker (crossed the spread). Advisory
  string fee_token = 11;       // fee token symbol (e.g. "USDC"); empty = unreported
}

message UserFillsResponse {
  repeated PerpFill fills = 1;
  bool success = 2;
  string error = 3;
}

GetUserFunding

Funding settlements (deltas) for a wallet over the same window.

rpc GetUserFunding(UserFundingRequest) returns (UserFundingResponse)

Request:

message UserFundingRequest {
  string venue = 1;
  string wallet_address = 2;
  string coin = 3;             // optional coin filter. Empty = all coins
  int64 start_time_ms = 4;     // optional lower bound, epoch ms. 0 = no bound
}

Response:

message PerpFundingDelta {
  string coin = 1;             // market symbol
  string usdc = 2;             // signed funding in USDC (- paid / + received; empty = unmeasured)
  string funding_rate = 3;     // rate applied for this settlement (empty = unmeasured)
  int64 time_ms = 4;           // settlement time, epoch ms
}

message UserFundingResponse {
  repeated PerpFundingDelta deltas = 1;
  bool success = 2;
  string error = 3;
}

GetOrderStatus

Status of a single submitted order, addressed by its client order id (cloid). This is the reject-detection signal the fill-reconciliation pump needs (VIB-5616): userFills alone cannot tell "not filled yet" (async settlement lag) from "rejected" — both leave the fills book empty — so a genuinely rejected open would otherwise stall a continuous perp strategy in PENDING until teardown. The gateway runs the connector's pure parser and returns the typed verdict; the strategy container never re-parses raw venue JSON. Empty≠Zero: a transport / decode fault returns success=false (the read could not complete); an order the venue does not know (unknownOid) is a measured response — success=true with an UNMEASURED status — so the pump stays PENDING but never fabricates a fill or reject.

rpc GetOrderStatus(OrderStatusRequest) returns (OrderStatusResponse)

Request:

message OrderStatusRequest {
  string venue = 1;            // one per perp connector; unknown -> INVALID_ARGUMENT
  string wallet_address = 2;   // account that submitted the order (EVM address)
  string cloid = 3;            // client order id as a decimal uint128 string. Required
  string chain = 4;            // chain hint (Hyperliquid is chain-agnostic)
}

Response:

message OrderStatusResponse {
  string status = 1;           // FillStatus: "filled"/"partially_filled"/"resting"/"rejected"/"unmeasured"
  string filled_size = 2;      // base-asset size filled (decimal string; empty = unmeasured)
  string avg_fill_price = 3;   // average fill price (decimal string; empty = unmeasured)
  string detail = 4;           // human-readable note (raw status / error) for observability
  bool success = 5;            // false -> UNMEASURED (never a fabricated verdict)
  string error = 6;
}

Error Codes

gRPC Code HTTP Description
INVALID_ARGUMENT 400 Invalid request parameters
NOT_FOUND 404 Resource not found
PERMISSION_DENIED 403 Operation not allowed
RESOURCE_EXHAUSTED 429 Rate limit exceeded
FAILED_PRECONDITION 412 Chain not configured
INTERNAL 500 Internal server error
UNAVAILABLE 503 Service temporarily unavailable

Rate Limits

Service Limit
RpcService (EVM chains) 300 req/min per chain
RpcService (Solana) 100 req/min
IntegrationService.Binance 1200 req/min
IntegrationService.CoinGecko 50 req/min (free tier)
IntegrationService.TheGraph 100 req/min