Skip to main content
The Graph’s GraphQL API supports offset-based pagination, field-level filtering, and sorting. Understanding these patterns is essential for building efficient queries against the FX Forward subgraph.

Pagination Basics

Every collection query accepts first and skip parameters:
ParameterTypeDefaultMaximumDescription
firstInt1001000Number of entities to return
skipInt05000Number of entities to skip
query Page($first: Int!, $skip: Int!) {
  positions(first: $first, skip: $skip, orderBy: openTimestamp, orderDirection: desc) {
    id
    notional
    status
  }
}
The absolute maximum for first is 1000 entities per query. For paginated UI tables, use a page size of 25 and increment skip by 25 per page. The skip parameter has a soft cap of 5000 — queries beyond this offset may time out on hosted services.

Sorting

Use orderBy and orderDirection together:
query SortedPositions {
  positions(orderBy: openTimestamp, orderDirection: desc, first: 25) {
    id
    openTimestamp
    notional
  }
}
orderDirection accepts asc (ascending) or desc (descending). If omitted, defaults to asc. Sortable fields include any scalar field on the entity (BigInt, Int, String, Bytes). Sorting by BigDecimal (e.g., sharePrice) is also supported.

Filtering with where

The where clause filters entities by field values. The Graph supports suffix-based operators:

Comparison Operators

SuffixMeaningExample
(none)Equalsstatus: "OPEN"
_notNot equalstatus_not: "OPEN"
_gtGreater thannotional_gt: "1000000000"
_gteGreater than or equalopenTimestamp_gte: "1700000000"
_ltLess thannotional_lt: "10000000000"
_lteLess than or equalcloseTimestamp_lte: "1700100000"
_inIn arraystatus_in: ["CLOSED_MATURITY", "LIQUIDATED"]
_not_inNot in arraystatus_not_in: ["OPEN"]
_containsContains (Bytes)pairId_contains: "0xab"

Combining Filters

All conditions in a where clause are ANDed:
query FilteredPositions($account: ID!, $minNotional: BigInt!) {
  positions(
    where: {
      account: $account
      status: "OPEN"
      notional_gte: $minNotional
    }
    first: 25
  ) {
    id
    notional
    entryStrike
  }
}

Time Range Queries

Combine _gte and _lte on timestamp fields to query date ranges:
query PositionsInRange($start: BigInt!, $end: BigInt!) {
  positions(
    where: { openTimestamp_gte: $start, openTimestamp_lte: $end }
    orderBy: openTimestamp
    orderDirection: asc
    first: 100
  ) {
    id
    account { id }
    notional
    openTimestamp
  }
}

Indexed Fields

Queries filter most efficiently on indexed fields. The subgraph indexes these fields with @index:
EntityIndexed Fields
Positionaccount, status, openTimestamp
OracleRoundisLatest
VaultEventtimestamp
PoolTransactiontimestamp
Filtering on non-indexed fields still works but may be slower on large datasets.

Pagination Patterns

Offset Pagination (Simple)

Suitable for UI tables with page numbers:
# Page 1
positions(first: 25, skip: 0, orderBy: openTimestamp, orderDirection: desc)

# Page 2
positions(first: 25, skip: 25, orderBy: openTimestamp, orderDirection: desc)

# Page N
positions(first: 25, skip: (N-1) * 25, orderBy: openTimestamp, orderDirection: desc)

Cursor Pagination (Large Datasets)

For datasets larger than 5000 entities, use the last entity’s sort field as a cursor:
# First page
query Page1 {
  positions(first: 1000, orderBy: openTimestamp, orderDirection: asc) {
    id
    openTimestamp
  }
}

# Next page — use the last openTimestamp from Page1
query Page2($cursor: BigInt!) {
  positions(
    first: 1000
    orderBy: openTimestamp
    orderDirection: asc
    where: { openTimestamp_gt: $cursor }
  ) {
    id
    openTimestamp
  }
}
This avoids the skip cap and performs better for deep pagination.

Fetching All Open Positions

When you need all open positions (e.g., for keeper batch operations), paginate with the cursor pattern:
query AllOpen($cursor: BigInt) {
  positions(
    where: { status: "OPEN", openTimestamp_gt: $cursor }
    orderBy: openTimestamp
    orderDirection: asc
    first: 1000
  ) {
    id
    fixingTimestamp
    openTimestamp
  }
}
Repeat until fewer than 1000 results are returned.

Derived Fields

The Account.positions field uses @derivedFrom, meaning it performs a reverse lookup at query time. Derived fields support the same pagination parameters:
query AccountPositions($account: ID!) {
  account(id: $account) {
    positions(first: 25, orderBy: openTimestamp, orderDirection: desc) {
      id
      status
      notional
    }
  }
}

Query Examples

Ready-to-use query patterns.

Schema Reference

Entity definitions and field types.

Data Source Guide

Choosing between subgraph and RPC.