Search code examples
graphqlblockchainsmartcontractssubgraphthegraph

The Graph: How can I include in my response this "price" value that exists in a different GraphQL type?


I'm working with thegraph.com and created a subgraph to index blockchain data from a NFT marketplace smart contract I'm building from scratch, just for my own educational purposes. At the same time I'm using using GraphQL and building queries here for the first time, so I'm not even sure if this is the correct question, but I'll get to the point of what's my ultimate goal with the screenshot below. Here is the subgraph endpoint I'm working with in case someone wants to inspect the schema I have there and play with it: https://api.thegraph.com/subgraphs/name/parenthesislab/cubansea-mumbai-v9/graphql

My desired outcome: Receive this price value that exists in MarketTokenMinted type inside the corresponding ERC721tokens objects i'm getting on this single API response.

query response example screenshot

My current GraphQL query (produces the response from the screenshot above ^):

query FetchMarketTokens {
  account(id: "0xae198b77c760c8d547f796f57c469c0294592ab8") {
    id
    ERC721tokens(orderBy: identifier, orderDirection: desc, first: 10) {
      id
      identifier
      uri
    }
  }
  marketTokenMinteds(orderBy: tokenId, orderDirection: desc) {
    nftContract
    price
    tokenId
  }
}

My current GraphQL schema deployed to this subgraph in The Graph:

# schema.graphql

type MarketTokenMinted @entity {
  id: ID!
  itemId: BigInt! # uint256
  nftContract: Bytes! # address
  tokenId: BigInt! # uint256
  seller: Bytes! # address
  owner: Bytes! # address
  price: BigInt! # uint256
  sold: Boolean! # bool
}

type MarketTokenSold @entity {
  id: ID!
  itemId: BigInt! # uint256
  nftContract: Bytes! # address
  tokenId: BigInt! # uint256
  seller: Bytes! # address
  owner: Bytes! # address
  price: BigInt! # uint256
  sold: Boolean! # bool
}

type Account @entity {
    id: ID!
    asERC721: ERC721Contract
    ERC721tokens: [ERC721Token!]! @derivedFrom(field: "owner")
    ERC721operatorOwner: [ERC721Operator!]! @derivedFrom(field: "owner")
    ERC721operatorOperator: [ERC721Operator!]! @derivedFrom(field: "operator")
    ERC721transferFromEvent: [ERC721Transfer!]! @derivedFrom(field: "from")
    ERC721transferToEvent: [ERC721Transfer!]! @derivedFrom(field: "to")
    events: [Event!]! @derivedFrom(field: "emitter")
}
type ERC721Contract @entity {
    id: ID!
    asAccount: Account!
    supportsMetadata: Boolean
    name: String
    symbol: String
    tokens: [ERC721Token!]! @derivedFrom(field: "contract")
    operators: [ERC721Operator!]! @derivedFrom(field: "contract")
    transfers: [ERC721Transfer!]! @derivedFrom(field: "contract")
}
type ERC721Token @entity {
    id: ID!
    contract: ERC721Contract!
    identifier: BigInt!
    owner: Account!
    approval: Account!
    uri: String
    transfers: [ERC721Transfer!]! @derivedFrom(field: "token")
}
type ERC721Operator @entity {
    id: ID!
    contract: ERC721Contract!
    owner: Account!
    operator: Account!
    approved: Boolean!
}
type ERC721Transfer implements Event @entity {
    id: ID!
    emitter: Account!
    transaction: Transaction!
    timestamp: BigInt!
    contract: ERC721Contract!
    token: ERC721Token!
    from: Account!
    to: Account!
}
interface Event {
    id: ID!
    transaction: Transaction!
    emitter: Account!
    timestamp: BigInt!
}
type Transaction @entity {
    id: ID!
    timestamp: BigInt!
    blockNumber: BigInt!
    events: [Event!]! @derivedFrom(field: "transaction")
}

After countless hours trying to figure this out I'm not sure anymore what I'm missing or how to put this together to receive this price value along with the rest of the obtained data on each ERC721tokens objects received. Any help that sets me in the right direction is highly appreciated.


Solution

  • Finally found how, answering my own question here for future reference.

    This could be done by adding a price field into the ERC721Token entity of my schema. Then I was able to add mapping logic into the event returning price which would load the ERC721Token entity and save the price information there. This event was MarketTokenMinted.

    The final solution was put together as follows:

    The ERC721Token type with an optional price field added:

    type ERC721Token @entity {
      id: ID!
      contract: ERC721Contract!
      identifier: BigInt!
      owner: Account!
      approval: Account!
      uri: String
      transfers: [ERC721Transfer!]! @derivedFrom(field: "token")
      price: BigInt # uint256
    }
    

    The mapping.ts file with added mapping logic to fetch the token by tokenId parameter (coming from the MarketTokenMintedEvent), adds the .price property and value and save with token.save():

    import {
      MarketTokenMinted as MarketTokenMintedEvent,
      MarketTokenSold as MarketTokenSoldEvent,
    } from "../generated/CSMarket/CSMarket";
    import { MarketTokenMinted, MarketTokenSold } from "../generated/schema";
    import { fetchERC721, fetchERC721Token } from "./fetch/erc721";
    
    export function handleMarketTokenMinted(event: MarketTokenMintedEvent): void {
      let entity = new MarketTokenMinted(
        event.transaction.hash.toHex() + "-" + event.logIndex.toString()
      );
      entity.itemId = event.params.itemId;
      entity.nftContract = event.params.nftContract;
      entity.tokenId = event.params.tokenId;
      entity.seller = event.params.seller;
      entity.owner = event.params.owner;
      entity.price = event.params.price;
      entity.sold = event.params.sold;
    
      // Add token price value to new .price field in the ERC721Token type.
      let contract = fetchERC721(event.params.nftContract);
      if (contract != null) {
        let token = fetchERC721Token(contract, event.params.tokenId);
    
        token.price = event.params.price;
    
        contract.save();
        token.save();
      }
    
      entity.save();
    }
    

    The FetchMarketTokensByOwner adding the price field:

    query FetchMarketTokensByOwner {
      erc721Tokens(
        where: {owner: "0xae198b77c760c8d547f796f57c469c0294592ab8"}
        orderBy: identifier
        orderDirection: desc
        first: 10
      ) {
        identifier
        uri
        owner {
          id
        }
        price
      }
    }
    

    And the results I'm getting now with the price value included inside each token object:

    {
      "data": {
        "erc721Tokens": [
          {
            "identifier": "4",
            "uri": "https://ipfs.infura.io/ipfs/QmfZXdugdU6BwtxWDNxpnETviB36qBX9qrLDrxeDSoFept",
            "owner": {
              "id": "0xae198b77c760c8d547f796f57c469c0294592ab8"
            },
            "price": "324324520000000"
          },
          {
            "identifier": "3",
            "uri": "https://ipfs.infura.io/ipfs/QmW21btPRbB8zgXiLs4oegpGXiLLSX9jnuSip4axwkFdaz",
            "owner": {
              "id": "0xae198b77c760c8d547f796f57c469c0294592ab8"
            },
            "price": "343235000000000"
          },
          {
            "identifier": "1",
            "uri": "https://ipfs.infura.io/ipfs/QmRKGJMnnLMBeT72bXU6yjG2g2MpHSvrq8pRGbZykmjSgE",
            "owner": {
              "id": "0xae198b77c760c8d547f796f57c469c0294592ab8"
            },
            "price": "323424523400000"
          }
        ]
      }
    }