Overview DApps such as NFT markets constantly need to retrieve on-chain data pertaining to token transfers or asset minting. One way to obtain that information is to pull it directly from the blockchain. This is often a complicated process which requires building an ad-hoc server instance. Instead, you can use a dedicated protocol like The Graph , which focuses on smart contract data indexing and storage.
1. How does The Graph work? The Graph is a decentralized protocol for querying blockchains like the Palm network. It enables developers to build custom APIs called Subgraphs.
What are Subgraphs? Subgraphs listen to specific smart contract events and act as data stores so that the information can easily be retrieved by a DApp. Since Subgraphs are built around developers’ specific data requirements (e.g. IEP-721 subgraph schema ) they only need to be called once, unlike general-purpose APIs which sometimes require hundreds of calls to gather relevant information.
Learn more about The Graph network and Protocol
2. Querying the Palm network’s Subgraphs The Graph provides a GraphQL API . If you are familiar with GraphQL, you should be comfortable querying The Graph. Make sure to learn the specifics of The Graph’s API .
Beginner GraphQL Resources Resources to Experiment with Subgraphs and GraphQL (optional)
A training toolkit to deploy a contract and immediately mint NFTs on the Palm Network GraphiQL tests queries against deployed Subgraphs and the NFTs you’ve minted 3. Available Subgraphs on the Palm network The Palm network provides a Graph node and a number of deployed Subgraphs tailored for NFT contexts. Developers can access those Subgraphs through RESTful or WebSocket APIs calls:
Mainnet Endpoint/GraphiQL
Testnet Endpoint/GraphiQL
Querying the Palm Sushi exchange
nftx-project/nftx-v2-subgraph
Querying Palm mainnet’s blocks
4. Using the Graph from a DApp A variety of tools are available to consume Subgraphs from applications based on:
React
Vue
iOS
Android
React Native
You can use a fully-featured package such as Apollo or a leaner implementation like GraphQL-Request .
Sample code for a React DApp { % raw % }
import { gql } from "@apollo/client" ;
import React, { useState } from "react" ;
import {
Chains,
Subgraph,
Subgraphs,
TheGraphProvider,
useCreateSubgraph,
useSubgraph,
} from "thegraph-react" ;
function PalmNfts ( { NFTs } : { readonly NFTs: Subgraph } ) : JSX . Element {
const { useQuery } = useSubgraph ( NFTs) ;
const { error, loading, data } = useQuery ( gql`
{
tokens(
where: { contract: "0xaadc2d4261199ce24a4b0a57370c4fcf43bb60aa" }
first: 10
) {
id
owner {
id
numTokens
}
contract {
name
symbol
}
tokenURI
}
}
` ) ;
console . log ( "data from GQL query:" , data) ;
return (
< div >
< div >
< div style= { { flex: 1 , textAlign: "left" } } >
< h1> Tokens Dashboard< / h1>
< / div>
< div style= { { textAlign: "left" , flex: 2 , alignSelf: "center" } } > < / div>
< / div>
{ error || loading ? (
< blockquote>
< br / >
< br / >
Loading...
< / blockquote>
) : (
( data as any ) . tokens. map ( ( n: any , i: number ) => {
return (
< div>
< div> Tokens name: { ` " ${ n. contract. name. toString ( ) } " ` } < / div>
< br / >
< div>
Owner wallet public address: { ` " ${ n. owner. id. toString ( ) } " ` }
< / div>
< br / >
< div>
Number of NFTs owned by this address: { ` " ${ n. owner. numTokens} " ` }
< / div>
< br / >
< div> Token URI : { n. tokenURI. toString ( ) } < / div>
< br / >
< div > < / div>
< br / > < div> < / div>
< / div>
) ;
} )
) }
< / div>
) ;
}
export default function App ( ) : JSX . Element {
const NFTs = useCreateSubgraph ( {
[ Chains. MAINNET ] :
"https://graph.palm.io/subgraphs/name/wighawag/eip721-subgraph" ,
} ) ;
const subgraphs = React. useMemo ( ( ) : Subgraphs => {
return [ NFTs] ;
} , [ NFTs] ) ;
return (
< TheGraphProvider chain= { Chains. MAINNET } subgraphs= { subgraphs} >
< PalmNfts NFTs= { NFTs} / >
< / TheGraphProvider>
) ;
}
{ % endraw % }
5. Build a New Subgraph Creating a subgraph allows to determine the data that the graph will index from the blockchain. It will then decide how this data will be stored.
To do that:
Deploy smart contracts that will be indexed (and their addresses)
Subgraph's manifest i.e. a config file, holds information about the smart contracts indexed by a Subgraph TypeScript code used to translate contracts’ event data into specific objects in the schema
To get an idea of the usual structure of a subgraph repository you can use graphprotocol’s example subgraph for a start. Learn More on NFT-specific repositories dedicated to:
Example Files There is no particular order to follow when modifying the files required to create a Subgraph.
Once done editing the 3 files below to define your subgraph, you can then optionally test your mapping in a sandbox environment and start the process of adding your subgraph to the Palm network.
specVersion: 0.0.2
description: EIP-721
repository: https://github.com/wighawag/eip721-subgraph
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum/contract
name: EIP721
network: Palm-mainnet
source:
address: '0x2E645469f354BB4F5c8a05B3b30A929361cf77eC'
abi: EIP721
startBlock: 5806610
mapping:
kind: ethereum/events
apiVersion: 0.0.4
language: wasm/assemblyscript
entities:
- Token
- TokenContract
- Owner
- OwnerPerTokenContract
- All
abis:
- name: EIP721
file: ./abis/All.json
eventHandlers:
- event: Transfer(indexed address,indexed address,indexed uint256)
handler: handleTransfer
file: ./src/mapping.ts
License [1]
Designed Around EIP-721 Compliant Contracts type All @entity {
id : ID !
numTokenContracts : BigInt !
numTokens : BigInt !
numOwners : BigInt !
}
type Token @entity {
id : ID !
contract : TokenContract !
tokenID : BigInt !
owner : Owner !
mintTime : BigInt !
tokenURI : String !
}
type TokenContract @entity {
id : ID !
name : String ,
symbol : String ,
doAllAddressesOwnTheirIdByDefault : Boolean !
supportsEIP721Metadata : Boolean !
tokens : [ Token ! ] ! @derivedFrom ( field : "contract" )
numTokens : BigInt !
numOwners : BigInt !
}
type Owner @entity {
id : ID !
tokens : [ Token ! ] ! @derivedFrom ( field : "owner" )
numTokens : BigInt !
}
type OwnerPerTokenContract @entity {
id : ID !
owner : Owner !
contract : TokenContract !
numTokens : BigInt !
}
License [1:1]
Mappings File import { store, Bytes, BigInt } from '@graphprotocol/graph-ts' ;
import { Transfer, EIP721 } from '../generated/EIP721/EIP721' ;
import { Token, TokenContract, Owner, All, OwnerPerTokenContract } from '../generated/schema' ;
let zeroAddress = '0x0000000000000000000000000000000000000000' ;
function toBytes ( hexString : String) : Bytes {
let result = new Uint8Array ( hexString. length / 2 ) ;
for ( let i = 0 ; i < hexString. length; i += 2 ) {
result[ i / 2 ] = parseInt ( hexString. substr ( i, 2 ) , 16 ) as u32;
}
return result as Bytes;
}
function supportsInterface ( contract : EIP721 , interfaceId : String, expected : boolean = true ) : boolean {
let supports = contract. try_supportsInterface ( toBytes ( interfaceId) ) ;
return ! supports. reverted && supports. value == expected;
}
function setCharAt ( str : string, index : i32, char : string) : string {
if ( index > str. length - 1 ) return str;
return str. substr ( 0 , index) + char + str. substr ( index + 1 ) ;
}
function normalize ( strValue : string) : string {
if ( strValue. length === 1 && strValue. charCodeAt ( 0 ) === 0 ) {
return "" ;
} else {
for ( let i = 0 ; i < strValue. length; i++ ) {
if ( strValue. charCodeAt ( i) === 0 ) {
strValue = setCharAt ( strValue, i, '\ufffd' ) ;
}
}
return strValue;
}
}
export function handleTransfer ( event : Transfer) : void {
let tokenId = event. params. id;
let id = event. address. toHex ( ) + '_' + tokenId. toString ( ) ;
let contractId = event. address. toHex ( ) ;
let from = event. params. from. toHex ( ) ;
let to = event. params. to. toHex ( ) ;
let all = All. load ( 'all' ) ;
if ( all == null ) {
all = new All ( 'all' ) ;
all. numOwners = BigInt. fromI32 ( 0 ) ;
all. numTokens = BigInt. fromI32 ( 0 ) ;
all. numTokenContracts = BigInt. fromI32 ( 0 ) ;
}
let contract = EIP721 . bind ( event. address) ;
let tokenContract = TokenContract. load ( contractId) ;
if ( tokenContract == null ) {
let supportsEIP165Identifier = supportsInterface ( contract, '01ffc9a7' ) ;
let supportsEIP721Identifier = supportsInterface ( contract, '80ac58cd' ) ;
let supportsNullIdentifierFalse = supportsInterface ( contract, '00000000' , false ) ;
let supportsEIP721 = supportsEIP165Identifier && supportsEIP721Identifier && supportsNullIdentifierFalse;
License [1:2]
6. How to add your own subgraph to the Palm network Deploy your smart contracts & obtain their addresses for the subgraph manifest
Create a config file for Palm testnet & mainnet with required info network : palm-mainnet or palm-testnet
Grant access to the Palm network team to the relevant subgraph code repository
The palm network team will review the subgraph & deploy it into testnet environments
You will be then able to validate that the test subgraph is working correctly
The Palm network team will deploy the subgraph to mainnet environments
You will now validate that the subgraph on Palm mainnet is working correctly
The Palm network team will document your subgraph’s endpoint on the docs.palm.io page
Copyright (C) <2019-2020> Ronan Sandford - GNU - https://github.com/wighawag/eip721-subgraph ↩︎ ↩︎ ↩
👉 🔥