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

# GraphQL conventions

> How the Windrose GraphQL API is shaped: one query per table, cursor pagination, filter operators, relations, and the units every number is in.

The GraphQL API is generated by Ponder from the indexer's schema, so every table follows the same shape. Learn the conventions once and every query page reads the same way.

## Two queries per table

Each table gets a singular query keyed by its primary key and a plural query that lists rows.

| Table           | Singular                                | Plural           | Primary key                    |
| --------------- | --------------------------------------- | ---------------- | ------------------------------ |
| `launch`        | `launch(token:)`                        | `launchs`        | `token` (launch token address) |
| `trade`         | `trade(id:)`                            | `trades`         | `id` = `txHash-logIndex`       |
| `candle`        | `candle(launch:, interval:, openTime:)` | `candles`        | composite                      |
| `holder`        | `holder(launch:, address:)`             | `holders`        | composite                      |
| `currency`      | `currency(code:)`                       | `currencys`      | `code` (bytes32 hex)           |
| `vaultSnapshot` | `vaultSnapshot(id:)`                    | `vaultSnapshots` | `id`                           |
| `vaultEvent`    | `vaultEvent(id:)`                       | `vaultEvents`    | `id`                           |
| `rate`          | `rate(id:)`                             | `rates`          | `id`                           |
| `feeClaim`      | `feeClaim(id:)`                         | `feeClaims`      | `id`                           |
| `curve`         | `curve(address:)`                       | `curves`         | `address`                      |
| `pool`          | `pool(poolId:)`                         | `pools`          | `poolId`                       |

<Warning>
  Ponder pluralises by appending `s`, so the plural queries are `launchs` and `currencys`, not `launches` and `currencies`.
</Warning>

The plural queries take the same arguments everywhere:

```graphql theme={"system"}
launchs(
  where: { currency: "INR", graduated: false }
  orderBy: "volumeQuote"
  orderDirection: "desc"
  limit: 20
  after: "<cursor>"
) {
  items { token symbol volumeQuote }
  pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
  totalCount
}
```

| Argument           | Type                | Notes                                                       |
| ------------------ | ------------------- | ----------------------------------------------------------- |
| `where`            | object              | Column filters, combined with AND. See [filters](#filters). |
| `orderBy`          | string              | A column name. Unknown columns are an error.                |
| `orderDirection`   | `"asc"` or `"desc"` | Default ascending.                                          |
| `limit`            | int                 | Default 50, maximum 1000. A larger value is rejected.       |
| `after` / `before` | string              | Cursors from `pageInfo`. Use one or the other, not both.    |

`totalCount` is only computed when you select it, so leave it out of queries you run often.

## Pagination

Page forward with `after: endCursor` while `hasNextPage` is true, keeping `where`, `orderBy` and `orderDirection` identical between calls:

```graphql theme={"system"}
query Trades($token: String!, $after: String) {
  trades(where: { launch: $token }, orderBy: "timestamp", orderDirection: "desc", limit: 100, after: $after) {
    items { id type quoteAmount tokenAmount timestamp }
    pageInfo { hasNextPage endCursor }
  }
}
```

Cursors are opaque strings tied to the ordering; do not build them by hand.

## Filters

The `where` object accepts the column name for equality plus operator suffixes. Which suffixes exist depends on the column type:

| Column type                    | Operators                                                                                                                                              |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| every scalar                   | `<col>` (equals), `<col>_not`, `<col>_in: [...]`, `<col>_not_in: [...]`                                                                                |
| numbers and bigints            | `<col>_gt`, `<col>_gte`, `<col>_lt`, `<col>_lte`                                                                                                       |
| text                           | `<col>_contains`, `<col>_not_contains`, `<col>_starts_with`, `<col>_not_starts_with`, `<col>_ends_with`, `<col>_not_ends_with`, each also as `_nocase` |
| hex (addresses, codes, hashes) | the text operators without the `_nocase` variants                                                                                                      |
| booleans                       | equals and `_not` only                                                                                                                                 |

Bigint comparisons take the value as a string:

```graphql theme={"system"}
{
  holders(where: { launch: "0x...", balance_gt: "1000000000000000000000" }) {
    items { address balance }
  }
}
```

Hex values are stored lowercase, so always lowercase addresses, transaction hashes, pool ids and codes in filters. A checksummed address does not match.

## Currency codes

Currencies are keyed by their `bytes32` code: the ASCII code right-padded with zeros, written as a 66-character hex string. `INR` is

```
0x494e520000000000000000000000000000000000000000000000000000000000
```

Tables that have a decoded text column (`launch.currency`, `currency.symbol`) let you filter on the readable code instead. `rate`, `vaultEvent` and `vaultSnapshot` only carry the hex `code`, so encode it first:

<CodeGroup>
  ```javascript viem theme={"system"}
  import { stringToHex } from "viem";
  const code = stringToHex("INR", { size: 32 }).toLowerCase();
  ```

  ```python python theme={"system"}
  code = "0x" + "INR".encode().ljust(32, b"\0").hex()
  ```
</CodeGroup>

`USD` is always registered. Its rate is fixed at `1e18` and never appears in `rates`.

## Relations

Relations let you nest one table inside another instead of issuing a second query. Nested lists take the same `where`, `orderBy`, `orderDirection` and `limit` arguments.

| From                                    | Field                                                | To                            |
| --------------------------------------- | ---------------------------------------------------- | ----------------------------- |
| `launch`                                | `trades`, `candles`, `holders`, `feeClaims`          | lists filtered to that launch |
| `launch`                                | `currencyInfo`                                       | the launch's `currency` row   |
| `trade`, `candle`, `holder`, `feeClaim` | `launchInfo`                                         | the parent `launch`           |
| `currency`                              | `launches`, `rates`, `vaultEvents`, `vaultSnapshots` | lists for that code           |
| `rate`, `vaultEvent`, `vaultSnapshot`   | `currencyInfo`                                       | the `currency` row            |

```graphql theme={"system"}
{
  launch(token: "0x...") {
    symbol currency
    currencyInfo { symbol tier vault lastRate }
    trades(orderBy: "timestamp", orderDirection: "desc", limit: 5) {
      items { type quoteAmount priceQuote timestamp }
    }
  }
}
```

## Scalars and units

Every `bigint` column is returned as a decimal string, and every hex column as a lowercase `0x` string. Timestamps are unix seconds.

| Value                                                                                                                                   | Unit                                                                                                                         |
| --------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| Token amounts (`tokenAmount`, `tokensSold`, `holder.balance`, `curveSupply`, `virtualToken`)                                            | 18 decimals; every launch has a fixed supply of 1e27 (one billion tokens)                                                    |
| Quote amounts (`quoteAmount`, `fee`, `volumeQuote`, `candle.volume`, `feeClaim.amount`, `realQuote`, `virtualQuote`, `graduationQuote`) | quote units: the quote asset normalised to 18 decimals (`raw * 10^(18 - quoteDecimals)`), the unit the curve uses internally |
| Prices (`priceQuote`, `lastPriceQuote`, candle `open`, `high`, `low`, `close`)                                                          | quote units per whole token, 1e18 fixed point (the same number as `BondingCurve.spotPrice()`)                                |
| USD prices (`priceUsd`, `lastPriceUsd`)                                                                                                 | `priceQuote * 1e18 / rate`, 1e18 fixed point; `null` until a rate for the launch's currency has been indexed                 |
| `rate.rate`, `currency.lastRate`, `vaultSnapshot.rate`, `vaultEvent.rate`                                                               | units of the currency per 1 USD, 1e18 fixed point; USD is `1e18`                                                             |
| Vault dollar amounts (`usdcAmount`, `assets`, `liability`, `equity`, `vaultEvent.fee`)                                                  | the chain's dollar in 6 decimals (USDG on Robinhood Chain, USDC on Arc); `equity` can be negative                            |
| `synthAmount`, `synthSupply`, `shares`, `shareSupply`                                                                                   | 18 decimals                                                                                                                  |
| `crBps`                                                                                                                                 | basis points (10,000 = 100%); `2^256 - 1` when the vault has no liability                                                    |
| `interval`                                                                                                                              | seconds: `60` or `3600`                                                                                                      |

A USD price for display is `priceQuote / rate`. A price in any other currency is the USD price multiplied by that currency's rate. The [units page](/protocol/units) explains the same conventions from the contract side.

<Tip>
  `quoteDecimals` on the launch tells you how to turn quote units back into the token's own unit: divide by `10^(18 - quoteDecimals)` to get the raw ERC-20 amount, or by `1e18` to get a human amount of the currency.
</Tip>

## Semantics worth knowing

* `trade.priceQuote` is the execution price of that trade, `quoteAmount / tokenAmount`. `launch.lastPriceQuote` and the candles use the spot price after the trade: the curve's `spotPrice()` recomputed from the event's `tokensSold` and `realQuote` while the launch is on the curve, and the pool price derived from `sqrtPriceX96` after graduation.
* A buy's `quoteAmount` is the gross quote paid, fee included. A sell's `quoteAmount` is the net quote received. `fee` is stored separately in both cases. For pool swaps `fee` is an estimate: the pool fee in pips applied to the input amount and converted to quote units.
* `trade.trader` is the transaction sender. The contract-level caller is normally the Router or the Factory.
* `holder` rows exclude the bonding curve, the Uniswap v4 `PoolManager` and the burn address, and rows are deleted when a balance reaches zero, so `launch.holderCount` equals the number of `holder` rows for that launch.
* `vaultSnapshot` reads `vault.status()` at the event's block. When `status()` reverts because the oracle price is older than its stale limit, the same accounting is recomputed from `vault.assets()` and the last known rate, and the row is written with `fresh: false`.
* `currency` rows are seeded from the registry the first time any handler needs them and kept in sync by `CurrencySet` and `RatePosted`. The oracle may post codes the registry does not list; those land in `rates` without a `currency` row.
* On Robinhood Chain post-graduation swaps are not indexed (see [how the data is produced](/api#how-the-data-is-produced)). `tradeCount`, `volumeQuote` and the candles of a graduated launch cover its curve phase only there.

## HTTP endpoints

The non-GraphQL routes (`/health`, `/ready`, `/status`, `/metrics`) and the web app's `/api/chains` and `/api/deployment` are described by the OpenAPI document in this section's "HTTP endpoints" group, with an interactive playground per endpoint. `POST /graphql` is listed there too so you can try a query without leaving the docs.
