SubgraphGraphQL recipes

GraphQL recipes

Every query in this page is ready to paste into the HyperIndex playground (or your urql / graphql-request client). The exact field names match the schema in Entity reference; the Entity_aggregate counterparts are HyperIndex-generated and follow the standard Hasura naming convention.

All entities are namespaced by chain. Always include chainId: { _eq: $chainId } in production filters to keep mainnet and Sepolia rows disambiguated.

All listings (including floors) for a collection

The FE typically wants the union of:

  • Floor items (format: NONE) — owned by the protocol, redeemable at the floor price.
  • Active liquid + dutch listings (format_in: [LIQUID, DUTCH]).

Both live on the same Listing table; filter by format and order by createdAt.

query AllListings($chainId: Int!, $collection: String!) {
  Listing(
    where: {
      chainId: { _eq: $chainId }
      collection: { collection: { _eq: $collection } }
    }
    order_by: { createdAt: desc }
    limit: 200
  ) {
    id
    tokenId
    owner
    format
    floorMultiple
    duration
    created
    taxCaptured
  }
}

For a paginated UI:

query AllListingsPaged(
  $chainId: Int!
  $collection: String!
  $limit: Int!
  $offset: Int!
) {
  Listing(
    where: {
      chainId: { _eq: $chainId }
      collection: { collection: { _eq: $collection } }
    }
    order_by: { createdAt: desc }
    limit: $limit
    offset: $offset
  ) {
    id
    tokenId
    owner
    format
    floorMultiple
  }
 
  Listing_aggregate(
    where: {
      chainId: { _eq: $chainId }
      collection: { collection: { _eq: $collection } }
    }
  ) {
    aggregate { count }
  }
}

Live pool state for a collection

query PoolState($chainId: Int!, $collection: String!) {
  Collection(
    where: {
      chainId: { _eq: $chainId }
      collection: { _eq: $collection }
    }
    limit: 1
  ) {
    id
    collectionToken
    poolKey
    sqrtPriceX96
    tick
    liquidity
    swapFee
    protocolFee
    defaultFee
    poolFee
    publicListings
    shutdownPrevented
    updatedAt
 
    poolFees {
      queuedAmount0
      queuedAmount1
      totalDonated0
      totalDonated1
      updatedAt
    }
  }
}

queuedAmount0 / queuedAmount1 are denominated in V4-sorted currency order (currency0 < currency1). Decode poolKey to figure out which side is the native token vs the CollectionToken.

Recent pool-fee donations

Useful for an “Activity” feed showing when the hook has flushed accumulated fees back to LPs.

query RecentPoolFeeDonations($chainId: Int!, $collection: String!) {
  PoolFeeDonation(
    where: {
      chainId: { _eq: $chainId }
      collection: { collection: { _eq: $collection } }
    }
    order_by: { created: desc }
    limit: 50
  ) {
    amount0
    amount1
    txHash
    triggeredBy
    created
  }
}

A user’s listing activity

query UserListingActivity($chainId: Int!, $user: String!) {
  ListingActivity(
    where: {
      chainId: { _eq: $chainId }
      _or: [{ from: { _eq: $user } }, { to: { _eq: $user } }]
    }
    order_by: { created: desc }
    limit: 100
  ) {
    activityType
    listing { id tokenId format owner }
    collection { id collection name }
    floorMultiple
    ethPrice
    txHash
    created
  }
}

A user’s claimable escrow

This includes both listing refunds and AMM fees — the entity is a single union since Listings and NFTXV4Hook both inherit TokenEscrow.

query UserEscrow($chainId: Int!, $payee: String!) {
  TokenEscrow(
    where: {
      chainId: { _eq: $chainId }
      payee: { _eq: $payee }
      amount: { _gt: "0" }
    }
    order_by: { updated: desc }
  ) {
    token
    amount
    updated
  }
}

Active shutdown for a collection (with my vote)

query ShutdownState(
  $chainId: Int!
  $collection: String!
  $voter: String!
) {
  CollectionShutdown(
    where: {
      chainId: { _eq: $chainId }
      collection: { collection: { _eq: $collection } }
    }
    limit: 1
  ) {
    id
    quorum
    quorumReachedAt
    executedAt
    liquidationPool
    claimAvailable
 
    votes(where: { voter: { _eq: $voter } }) {
      votes
      createdAt
    }
 
    claims(where: { claimant: { _eq: $voter } }) {
      tokensBurnt
      ethReceived
      claimedAt
      txHash
    }
  }
}

All shutdowns past quorum but not yet executed

Operationally, this is what surfaces a “Click to execute” button.

query ExecutableShutdowns($chainId: Int!) {
  CollectionShutdown(
    where: {
      chainId: { _eq: $chainId }
      quorumReachedAt: { _is_null: false }
      executedAt: { _is_null: true }
    }
    order_by: { quorumReachedAt: asc }
  ) {
    id
    collection { collection name }
    quorum
    quorumReachedAt
    startedBy
    startedAt
  }
}

Available airdrops for recipient

Airdrops are merkle-distributed; the subgraph indexes the distribution header and every claim. To know what recipient can still claim you need the off-chain merkle service to list eligible leaves; the subgraph is best used to filter out already-claimed ones:

query AirdropClaimsForRecipient($chainId: Int!, $recipient: String!) {
  AirdropClaim(
    where: {
      chainId: { _eq: $chainId }
      recipient: { _eq: $recipient }
    }
    order_by: { claimedAt: desc }
    limit: 100
  ) {
    airdrop { id merkle claimType }
    target
    tokenId
    amount
    txHash
    claimedAt
  }
}

For the “is this target allowed” check before showing the request UI:

query AirdropTargetAllowed($chainId: Int!, $target: String!) {
  AirdropTarget(
    where: {
      chainId: { _eq: $chainId }
      target: { _eq: $target }
    }
    limit: 1
  ) { allowed updatedAt }
}

AMM fees taken in last 24h

query AmmFeesLast24h($chainId: Int!, $cutoff: numeric!) {
  AmmFeeTaken(
    where: {
      chainId: { _eq: $chainId }
      created: { _gt: $cutoff }
    }
    order_by: { created: desc }
  ) {
    recipient
    token
    amount
    txHash
    created
  }
}

$cutoff is a unix-second BigInt (e.g. now() - 86400). Aggregate the result client-side or use a HyperIndex AmmFeeTaken_aggregate { aggregate { sum { amount } } } if you only need a total.

Fee-override snapshot for a beneficiary

query FeeOverride($chainId: Int!, $beneficiary: String!) {
  FeeOverride(
    where: {
      chainId: { _eq: $chainId }
      beneficiary: { _eq: $beneficiary }
    }
    limit: 1
  ) { flatFee updatedAt }
}

A flatFee of null means the override has been removed; a non-null value overrides both the per-pool poolFee and the global defaultFee for swaps routed through that beneficiary.

Resolve a collection’s effective listing config

Per-collection override fields on Collection are null when no override is set — fall back to the matching Config default in that case.

query ListingConfig($chainId: Int!, $collection: String!) {
  Collection(
    where: { chainId: { _eq: $chainId }, collection: { _eq: $collection } }
    limit: 1
  ) {
    taxCalculator
    maxFloorMultiple
    minLiquidDuration
    maxLiquidDuration
    minDutchDuration
    maxDutchDuration
    liquidDutchDuration
  }
  Config(where: { chainId: { _eq: $chainId } }, limit: 1) {
    defaultTaxCalculator
    defaultMaxFloorMultiple
    defaultMinLiquidDuration
    defaultMaxLiquidDuration
    defaultMinDutchDuration
    defaultMaxDutchDuration
    defaultLiquidDutchDuration
  }
}

Approved Listings contracts

query ApprovedListingsContracts($chainId: Int!) {
  ListingsContract(
    where: { chainId: { _eq: $chainId }, approved: { _eq: true } }
  ) { listings updatedAt }
}