Introduction

Constellations is a high-performance and real-time indexer developed by Fabien Penso for Stargaze, a L1 blockchain on Cosmos focusing on NFTs. Constellations is currently used by Stargaze for its main website to display data in the fastest way possible, and we decided to make its API available to anyone.

Constellations is:

  1. A public facing website available at www.constellations.zone to view its data. This is mostly used to "validate" internal information, and you are invited to use the Stargaze main website instead, unless you're looking for information not available there.

  2. A set of API to fetch data Constellations has indexed. This API is available over GraphQL and self-generated GraphQL documentation is enabled.

You should already know about GraphQL to use Constellations. You can read more about GraphQL here.

How is it built?

Constellations is built entirely in Rust with tendermint-rs, backed with Posgresql and hosted on bare metal servers for optimum performance. It fetches blocks live from the blockchain over websockets, and parse them instantly. It currently is running on 3 servers:

  1. Dedibox: Xeon E3 1245v5, 32GB
  2. Hetzner: AMD Ryzen 3600, 64GB
  3. Interserver: AMD RYZEN 5600X, 64GB

It ran fine on a single server, but we wanted to have redundancy to avoid downtimes. These days it manages way over 10 million requests per day.

Safety

While I try to minimize bugs and issues, Constellations sometimes (but rarily) stops indexing, or has an indexing issue which means it does not reflect the blockchain data. It is highly recommended to use Stargaze RPC servers to verify information if you are moving tokens. For example, if you want to send STARS to a given Stargaze name, you should use the RPC server to get the STARS address associated with that given name, and not Constellations which could lead to sending STARS to an old associated address if the indexer stopped.

Timestamps

All timestamps you get are UTC and don't include timezone.

User Agent

Add a way to contact you in the User-Agent header in case I need to get in touch with you if you are making too many requests.

Throttling

There is no throttling in place for now but will be soon. Please don't hammer the server with too many calls.

Contributing to this documentation

This documentation is available on GitHub and issues and feature requests can be posted on the GitHub issue tracker. Please consider opening a pull request.

I can't find how to fetch specific data

Create an issue with as many details as possible, contract type, information you want and why you want to fetch it.

License

The Constellations documentation are released under the Mozilla Public License v2.0.

API Endpoints

There are 2 available GraphQL endpoints.

Mainnet

https://constellations-api.mainnet.stargaze-apis.com/graphql

Testnet

https://constellations-api.testnet.stargaze-apis.com/graphql

Pagination

Endpoints use both types pagination. When data isn't changing often, it uses offset-based pagination, when data is changing rapidly it uses cursor-based pagination.

The following is using offset pagination:

  • collections
  • names
  • tokens

The following is using cursor pagination:

  • events
  • sales

Collections

Query

query Collections($limit: Int, $sortBy: CollectionSortBy, $offset: Int, $tokenOwnerAddr: String, $creatorAddr: String) {
  collections(limit: $limit, sortBy: $sortBy, offset: $offset, tokenOwnerAddr: $tokenOwnerAddr, creatorAddr: $creatorAddr) {
    collections {
      image
      description
      tokensCount
      website
      createdAt
      collectionAddr
      name
      mintedAt
    }
    limit
    offset
    total
  }
}

Variables

{
  "limit": 2,
  "sortBy": null,
  "offset": null,
  "tokenOwnerAddr": null,
  "creatorAddr": null
}

Try it out live on explorer

Available sorting

  • TOKENS_COUNT_ASC: Sort by the smallest amount of tokens
  • TOKENS_COUNT_DESC: Sort by the highest amounf of tokens
  • MINTED_AT_ASC: Sort by the least recent minted collection time
  • MINTED_AT_DESC: Sort by the most recent minted collection time
  • VOLUME_24H_DESC: Sort by the past 24H sales volumes
  • VOLUME_7D_DESC: Sort by the past 7 days sales volumes

get all collections

GraphQL will have all internal attribute documentation.

query Collections {
  collections {
    collections {
      name
      mintedAt
    }
  }
}

Try it out live on explorer

get most recent minted collections

Query

query Collections($collectionsLimit: Int, $sortBy: CollectionSortBy) {
  collections(limit: $collectionsLimit, sortBy: $sortBy) {
    collections {
      image
      description
      tokensCount
      website
      createdAt
      collectionAddr
      name
      mintedAt
    }
  }
}

Variables

{
  "collectionsLimit": 2,
  "sortBy": "MINTED_AT_DESC"
}

Try it out live on explorer

list collections where an addr owns at least one token

query Collections {
  collections(tokenOwnerAddr: "stars1mtu407haz7g9s4hazdh8mtw94x3v52uaj8xx8c") {
    collections {
      name
    }
  }
}

Try it out live on explorer

list collections created by a given address

query Collections {
  collections(creatorAddr: "stars1err5c2tc8ha6qa42tpvedgk585dpj35576ny29") {
    collections {
      name
      mintedAt
      createdByAddr
    }
  }
}

Try it out live on explorer

Contracts

Contracts contains informations about deployed contracts.

query Contracts {
  contracts {
    contractAddr
    contractType
    contractInfo
    contractCodeId
    contractVersion
    contractLabel
    blocked
    blockHeight
    createdAt
    lastErrorAt
    updatedAt
  }
}

Try it out live on explorer

contract at a given address

query Contract {
  contract(address: "stars13qd5kyn3larzjqm86ywyfw7tglnrmraww4suc0nnc668kskhspesem9ssq") {
      contractAddr
      contractType
      contractInfo
      contractCodeId
      contractVersion
      contractLabel
      blocked
      blockHeight
      createdAt
      lastErrorAt
      updatedAt
    
  }
}

Try it out live on explorer

Events

Events contains all blockchain cosmwasm events, mints, airdrops, sales. You have to know what events you're looking for, or what contract types. Unless they're very noisy and useless, all cosmwasm events are saved.

If you are publishing a new contract, using events and attributes is the best way to ensure this will be indexed by Constellations.

For example, the following get all events related to the pegasus contract, order by block height DESC (most recent event first):

query Events {
  events(
    contractFilters: [{ contractType: "crates.io:pegasus", events: [] }]
    sortBy: BLOCK_HEIGHT_DESC
  ) {
    edges {
      node {
        eventName
        action
        data
        createdAt
      }
    }
  }
}

Try it out live on explorer

Or another example for badge-hub contracts:

query Events {
  events(
    contractFilters: [{ contractType: "crates.io:badge-hub", events: [] }]
    sortBy: BLOCK_HEIGHT_DESC
  ) {
    edges {
      node {
        eventName
        action
        data
        createdAt
      }
    }
  }
}

Try it out live on explorer

This will show all wasm-fair-burn for the badge-hub contract:

query Events {
  events(
    contractFilters: [
      {
        contractType: "crates.io:badge-hub"
        events: [{ name: "wasm-fair-burn" }]
      }
    ]
    sortBy: BLOCK_HEIGHT_DESC
  ) {
    edges {
      node {
        eventName
        action
        data
        createdAt
      }
    }
  }
}

Try it out live on explorer

get all burns

This will get all burns for both the sg-721 contracts, it will not include other contracts like imago contracts. You have to add contracts specifically if you want others.

query Burns($filters: [ContractFilter!], $dataFilters: [DataFilter!]) {
  events(contractFilters: $filters, dataFilters: $dataFilters) {
    edges {
      node {
        contractType
        data
        createdAt
      }
    }
  }
}
{
  "filters": [
    {
      "contractType": "crates.io:sg-721",
      "events": [
        {
          "name": "wasm",
          "action": "burn"
        }
      ]
    },
    {
      "contractType": "crates.io:sg721-base",
      "events": [
        {
          "name": "wasm",
          "action": "burn"
        }
      ]
    }
  ],
  "dataFilters": []
}

Try it out live on explorer

name mints

query NameMints {
  events(
    contractFilters: [
      {
        contractType: "crates.io:name-minter"
        events: [{ name: "wasm-mint-and-list", action: null }]
      }
    ]
  ) {
    edges {
      node {
        data
        createdAt
      }
    }
  }
}

Try it out live on explorer

token bids

query Bids($filters: [ContractFilter!], $dataFilters: [DataFilter!]) {
  events(contractFilters: $filters, dataFilters: $dataFilters) {
    edges {
      node {
        data
      }
    }
  }
}
{
  "filters": [
    {
      "contractType": "crates.io:sg-marketplace",
      "events": [
        {
          "name": "wasm-set-bid",
          "action": null
        }
      ]
    }
  ],
  "dataFilters": []
}

Try it out live on explorer

all events for a given collection

query EventsForCollection($data_filters: [DataFilter!]) {
  events(dataFilters: $data_filters) {
    edges {
      node {
        contractType
        eventName
        action
        data
        createdAt
      }
    }
  }
}
{
  "dataFilters": [
    {
      "name": "collection",
      "value": "stars18d7ver7mmjdt06mz6x0pz09862060kupju75kpka5j0r7huearcsq0gyg0",
      "operator": "EQUAL"
    }
  ]
}

Try it out live on explorer

all events for a given token

query EventsForTokenId {
  events(
    filter: TOKEN_METADATAS
    forToken: {
      collectionAddr: "stars1ery3ph276meayswezstaulm4vekzz9u825ppf8gx6cxjteplvrrqt33m8k"
      tokenId: "127"
    }
  ) {
    edges {
      node {
        contractType
        eventName
        action
        data
        createdAt
        blockHeight
      }
    }
  }
}

Try it out live on explorer

all offers sent for a given address

query SentOffersForGivenAddress {
  events(
    filter: SENT_NAME_OFFERS
    forAddresses: ["stars10l56qdud5g6qt3pzywy65urm9cle0mx7aur6zd"]
  ) {
    edges {
      node {
        contractType
        eventName
        action
        data
        createdAt
        blockHeight
        isValid
        expired
      }
    }
  }
}

Try it out live on explorer

bids for a given collection and given bidder

query BidsForGivenBidder {
  events(
    dataFilters: [
      {
        name: "bidder"
        value: "stars1rmxl4fps24pe8s9uv8an3nqpng3ggyf8sfavr0"
        operator: EQUAL
      }
      {
        name: "collection"
        value: "stars19jq6mj84cnt9p7sagjxqf8hxtczwc8wlpuwe4sh62w45aheseues57n420"
        operator: EQUAL
      }
    ]
    contractFilters: [
      {
        contractType: "crates.io:sg-marketplace"
        events: [{ name: "wasm-set-bid", action: null }]
      }
    ]
    sortBy: BLOCK_HEIGHT_DESC
  ) {
    edges {
      node {
        txHash
        action
        contractInfo
        createdAt
        isValid
        eventName
        action
        data
      }
    }
  }
}

Try it out live on explorer

received offers on owned NFT

query receivedOffersOnOwnedNfts {
  events(
    filter: RECEIVED_OFFERS_ON_OWNED_NFT
    forAddresses: ["stars15y38ehvexp6275ptmm4jj3qdds379nk07tw95r"]
    isValid: true
  ) {
    edges {
      node {
        eventName
        data
        isValid
      }
    }
  }
}

Try it out live on explorer

sales for a given collection

query Sales {
  events(
    filter: SALES
    dataFilters: [
      {
        name: "collection"
        value: "stars1ewpdt2s5t2ktkyzsv3rhe88yeyxpjrfvqg209rac5pn46gffr75qvt8rq3"
        operator: EQUAL
      }
    ]
    sortBy: BLOCK_HEIGHT_DESC
  ) {
    edges {
      node {
        eventName
        action
        createdAt
        data
      }
    }
  }
}

Try it out live on explorer

List of tokens current as live auctions

query ValidLiveAuctions {
  events(
    contractFilters: [
      {
        contractType: "crates.io:stargaze-reserve-auction"
        events: [{ name: "wasm-create-auction", action: null }]
      }
    ]
    dataFilters: [
      {
        name: "seller"
        value: "stars1rpnqgfdr79zv7zn6d8y888u7vqvpgtr5cqdnuw"
        operator: EQUAL
      }
    ]
    isValid: true
  ) {
    edges {
      node {
        data
      }
    }
  }
}

Try it out live on explorer

all sales

query Sales {
  events(
    filter: SALES
    sortBy: BLOCK_HEIGHT_DESC
  ) {
    edges {
      node {
        eventName
        action
        createdAt
        data
      }
    }
  }
}

Try it out live on explorer

Names

Names contains all the minted Stargaze names.

query Names {
  names {
    limit
    offset
    total
    names {
      name
      mintedAt
    }
  }
}

Try it out live on explorer

all names sorted by offer

query Names {
  names(sortBy: OFFERS_DESC) {
    limit
    offset
    total
    names {
      highestOffer
      highestOfferEvent {
        data
      }
      name
      mintedAt
      contractAddr
      ownerAddr
      associatedAddr
      records {
        recordName
        recordValue
        verified
      }
    }
  }
}

Try it out live on explorer

name with its record

query Name {
  name(name: "sex") {
    highestOfferEvent {
      data
    }
    highestOffer
    name
    mintedAt
    contractAddr
    ownerAddr
    associatedAddr
    records {
      recordName
      recordValue
      verified
    }
  }
}

Try it out live on explorer

query Name {
  name(name: "penso") {
    highestOfferEvent {
      data
    }
    highestOffer
    name
    mintedAt
    contractAddr
    ownerAddr
    associatedAddr
    records {
      recordName
      recordValue
      verified
    }
  }
}

Try it out live on explorer

name associated to a given address

query Name {
  names(associatedAddr: "stars1mtu407haz7g9s4hazdh8mtw94x3v52uaj8xx8c") {
    names {
      name
      mintedAt
    }
  }
}

Try it out live on explorer

Tokens

Tokens contains all NFT tokens. Always use a collection filter or the query will be slow

query Tokens {
  tokens(
    filterByCollectionAddrs: [
      "stars1g4ucjvrcpwwxrrtmnnrprwkvtnud43294k3mrvtnlpdk2uza7hxseqr58k"
    ]
  ) {
    tokens {
      collectionAddr
      tokenId
      mintedAt
      ownerAddr
      name
      description
      forSale
      imageUrl
      rarityOrder
      rarityScore
      price {
        amount
      }
      priceExpiresAt
      saleType
    }
  }
}

Try it out live on explorer

tokens for a given owner

query Tokens {
  tokens(
    ownerAddr: "stars1mtu407haz7g9s4hazdh8mtw94x3v52uaj8xx8c"
  ) {
    tokens {
      collectionAddr
      tokenId
      name
    }
  }
}

Try it out live on explorer

tokens for a given collection

query Tokens {
  tokens(filterByCollectionAddrs: ["stars1g4ucjvrcpwwxrrtmnnrprwkvtnud43294k3mrvtnlpdk2uza7hxseqr58k"]) {
    tokens {
      collectionAddr
      tokenId
      name
    }
  }
}

Try it out live on explorer

tokens listed as live auctions for a given collection

query Tokens {
  tokens(filterForSale: "LIVE_AUCTIONS") {
    tokens {
      collectionAddr
      tokenId
      forSale
      saleType
    }
  }
}

Try it out live on explorer

Contributors

Changelog

  • Added contracts
  • Added all sales
  • Added links to studio explore
  • Changed endpoints to use a load balancer