> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nilemarkets.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Shared API Architecture

> Three API surfaces (MCP server, x402 REST gateway, CLI) share a common data-access layer that provides config resolution, RPC clients, GraphQL queries, transaction encoding, and response metadata — eliminating duplication and ensuring behavioral consistency.

## Why Three Surfaces?

Each surface serves a different runtime environment, transport model, and authentication strategy. No single API format satisfies all three use cases.

| Surface    | Target User                                      | Transport                  | Auth Model                                 |
| ---------- | ------------------------------------------------ | -------------------------- | ------------------------------------------ |
| MCP Server | AI agents (Claude, ChatGPT, Cursor, VS Code)     | Streamable HTTP (JSON-RPC) | None                                       |
| x402 API   | Programmatic clients, pay-per-request agents     | REST (HTTP GET/POST)       | Free reads; \$0.01 USDC per write via x402 |
| CLI        | Shell-based agents, CI pipelines, terminal users | stdin/stdout (subprocess)  | None (local binary)                        |

All three expose the same 22 operations (15 read, 8 write) with the same data semantics. The difference is transport, auth, and output formatting.

## The Shared API Layer

Rather than duplicating data-access code across surfaces, a shared TypeScript module (`@nile-markets/api`) sits between the surfaces and the data sources. Each surface imports shared functions and adds only its transport-specific layer.

```
+-------------------+     +-------------------+     +-------------------+
|    MCP Server     |     |    x402 Gateway    |     |       CLI         |
|  (JSON-RPC tools) |     |  (REST endpoints)  |     |  (Rust commands)  |
+--------+----------+     +--------+----------+     +--------+----------+
         |                         |                          |
         |    TypeScript           |    TypeScript             |    Rust
         v                         v                          v
+--------+-------------------------+----------+     +--------+----------+
|              @nile-markets/api              |     |  crates/cli/      |
|  config | clients | queries | encoding      |     |  (mirrors same    |
|  types  | logger  | rate-limit | context    |     |   query semantics)|
+--------+-------------------------+----------+     +--------+----------+
         |                         |                          |
         v                         v                          v
+--------+-------------------------+-------------------------+----------+
|                  Ethereum RPC (viem / alloy-rs)                       |
+----------------------------------------------------------------------+
|                  The Graph Subgraph (GraphQL)                         |
+----------------------------------------------------------------------+
```

The two TypeScript surfaces import directly from `@nile-markets/api`. The Rust CLI implements the same logical queries independently via `graphql_client` derive macros, maintaining semantic parity.

## What Is Shared

### Config Resolution

Network configuration resolves through a consistent fallback chain across all surfaces:

```
RPC_URL_{NETWORK} > RPC_URL (legacy) > per-network default > error
```

The `withNetwork()` function sets the active network at each request's entry point using `AsyncLocalStorage`. All downstream calls (`getConfig()`, `getPublicClient()`, `getGraphQLClient()`) read from this context automatically — no parameter threading needed.

### RPC Clients

A cached `viem` `PublicClient` per network handles all onchain reads: contract state, forward prices, margin balances, oracle validity, and position simulation via `eth_call`. Clients are created once and reused across requests.

### GraphQL Queries

Seven shared subgraph query functions fetch historical and aggregate data:

| Query                   | Data                                                              |
| ----------------------- | ----------------------------------------------------------------- |
| `fetchPositions`        | Account positions with optional status filter                     |
| `searchPositions`       | Cross-account position search with side/tenor/notional filters    |
| `fetchPoolState`        | Pool singleton (TVL, shares, fees, exposure)                      |
| `fetchDailyStats`       | Daily trading volume, PnL, and fee aggregates                     |
| `fetchPoolTransactions` | Vault deposit/withdrawal history                                  |
| `fetchFeeEvents`        | Fee collection events by type                                     |
| `fetchAccount`          | Account aggregate stats (position count, realized PnL, LP shares) |

Some operations combine RPC and subgraph data. For example, `fetchPoolStateRpc()` reads real-time state (total assets, share price, utilization) from RPC and historical fee totals from the subgraph. If the subgraph is unavailable, it returns RPC data with zeroed aggregate fields rather than failing.

### Transaction Encoding

All write operations return unsigned calldata rather than executing transactions:

```json theme={null}
{
  "to": "0x...",
  "data": "0x...",
  "value": "0",
  "estimatedGas": "150000",
  "chainId": 11155111,
  "description": "Open LONG position with 100000000 notional"
}
```

Signing and broadcasting happen externally via the user's wallet. This keeps all surfaces zero-custody.

### Response Metadata

Every response includes domain-layer metadata with the same structure:

| Field              | Description                                               |
| ------------------ | --------------------------------------------------------- |
| `protocol.name`    | `"nile-markets"`                                          |
| `protocol.version` | `"0.3.2"` (single source of truth in `@nile-markets/api`) |
| `network`          | Active network (`"sepolia"`, `"local"`)                   |
| `blockNumber`      | Subgraph indexing head (when subgraph data is included)   |
| `source`           | `"rpc"`, `"subgraph"`, or `"rpc+subgraph"`                |

### Enum and Label Maps

Solidity enum ordinals (stored in contract state and subgraph) are mapped to human-readable labels by shared functions: `sideLabel()`, `tenorLabel()`, `modeLabel()`, `mapCloseReason()`, `feeTypeLabel()`. All surfaces use the same mappings, so a position shows `"LONG"` and `"1W"` regardless of whether you query via MCP, REST, or CLI.

## What Is Surface-Specific

Each surface adds only its transport and formatting layer on top of the shared API.

<Tabs>
  <Tab title="MCP Server">
    * **Transport**: Streamable HTTP with JSON-RPC framing via `mcp-handler`
    * **Tool registration**: MCP tool schemas with `name`, `description`, `inputSchema`
    * **Network context**: `withNetwork()` called per tool invocation
    * **Auth**: None
    * **Response format**: `content[0].text` JSON string inside MCP tool result
    * **Rate limiting**: Shared `createRateLimiter()` from `@nile-markets/api`
  </Tab>

  <Tab title="x402 Gateway">
    * **Transport**: Next.js API routes (GET for reads, POST for writes)
    * **Auth**: Free reads; writes require x402 micropayment header (\$0.01 USDC)
    * **Network context**: `withNetwork()` called per route handler from query param
    * **Response format**: REST JSON envelope (`{ success, data, protocol, network, timestamp }`)
    * **Rate limiting**: Inline middleware (separate from shared limiter due to x402 payment verification ordering)
  </Tab>

  <Tab title="CLI">
    * **Transport**: stdin/stdout subprocess, `--output json` flag for structured output
    * **Language**: Rust (cannot import TypeScript package directly)
    * **Auth**: None (local binary); wallet signing via OWS or external signer
    * **Network context**: `--network` flag or `NILE_NETWORK` env var
    * **Query parity**: Separate `.graphql` files in `crates/cli/graphql/` mirror shared query semantics
    * **Response format**: JSON or formatted table via `comfy-table`
  </Tab>
</Tabs>

## Adding a New Surface

To add a fourth API surface (WebSocket, gRPC, or another transport):

<Steps>
  <Step title="Import shared functions">
    Add `@nile-markets/api` as a workspace dependency. Import config, clients, queries, and encoding functions.
  </Step>

  <Step title="Set network context at entry point">
    Call `withNetwork(network, handler)` at each request boundary. All downstream calls resolve network automatically.
  </Step>

  <Step title="Add transport layer">
    Implement request parsing, response formatting, and auth for the new transport. The shared module handles all data access.
  </Step>

  <Step title="Update downstream sync rules">
    Add the new surface to the API surface parity checklist. Every MCP tool and x402 endpoint must have a corresponding operation in the new surface.
  </Step>
</Steps>

## Supporting two testnets

M2 is deployed on **both** Ethereum Sepolia (L1, chainId `11155111`) and Arbitrum Sepolia (L2, chainId `421614`). Both networks run identical contracts with identical parameters; the protocol surface is the same. The two-testnet choice is additive coverage, not a switch:

* **The L1 case.** Sepolia mirrors Ethereum mainnet's gas market and \~12 s confirmation cadence. Validating gas-cap behavior, fee-driven publisher pauses, and L1-scale settlement timing requires a live L1 testnet — without it, operators cannot rehearse mainnet conditions. Sepolia is also the canonical L1 testnet for ecosystem dependencies (Pyth, Graph Studio, third-party tooling).
* **The L2 case.** Arbitrum Sepolia provides a deployment target with the cost / latency profile that agent integrations (MCP / x402 / CLI) tend to want. A typical 5-tx agent task (approve USDC → deposit margin → open position → poll → close) settles at L1 cadence on Sepolia and at L2 cadence on Arb Sep; both behaviors are correct, but agent iteration loops that want sub-second confirmation need the L2 option.

Neither network is "better". They cover different validation regimes. Mainnet (M3) decisions for L1 vs L2 deployment are scoped separately and informed by data collected from both testnets.

### Choosing a network for agent flows

| Use case                                                                  | Recommended network                 |
| ------------------------------------------------------------------------- | ----------------------------------- |
| Validate behavior under mainnet-shape gas variance                        | Ethereum Sepolia                    |
| Validate L1 confirmation cadence (12 s blocks) and ecosystem dependencies | Ethereum Sepolia                    |
| Benchmark agent throughput / fast iteration loops                         | Arbitrum Sepolia                    |
| Long-running scenarios where fee predictability matters                   | Arbitrum Sepolia                    |
| End-to-end coverage of protocol on both fee regimes                       | Both — run integrations in parallel |

The protocol surface (function names, parameter types, revert conditions, response envelopes) is identical on both chains. Switching is a one-flag change: `--network arbitrumSepolia` on the CLI, `?network=arbitrumSepolia` on x402, `network` parameter on MCP, or wallet chain switch in the frontend.
