Amowe Olubusayo
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    1
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Writing a Subgraph for 2 ERC721 NFT collections # Graph Overview We can’t talk about a thriving, decentralized future with APIs without speaking about the Graph. The Graph enables the querying of blockchain data that is challenging to query directly. There are notable products using the Graph in production today e.g. Uniswap, Synthetic and Aragon. The collection, transformation and storage of information from a blockchain is done using open APIs known as subgraphs. Subgraphs query data via GraphQL. When you deploy a subgraph, you can perform several actions with it such as being able to call it from your frontend. To learn more about the graph, you can check them out here. The following makes up a subgraph: `subgraph.yaml:` The subgraph.yaml file is a YAML file that holds the subgraph manifest `schema.graphql:` The schema.graphql is a GraphQL schema outlining the information saved for your subgraph including how to query it using GraphQL `AssemblyScript Mappings`: This is the code in AssemblyScript that converts event data to the entities listed in your schema. In this tutorial, we would have a walk through on how to create a subgraph for two NFT collections (ERC721 contracts) and be using the hosted service. The following networks are supported on the hosted service | Ethereum | Near | Cosmos | Arweave | | -------- | ------------ | ----------- | --- | | mainnet | near-mainnet | cosmoshub-4 | arweave-mainnet | kovan | near-testnet | theta-testnet-001 rinkeby ropsten goerli poa-core poa-sokol xdai (now known as Gnosis Chain) matic (now known as Polygon) mumbai fantom bsc (now known as BNB Chain) chapel clover avalanche fuji celo celo-alfajores fuse moonriver mbase arbitrum-one arbitrum-rinkeby optimism optimism-kovan aurora aurora-testnet boba harmony | | | ![](https://i.imgur.com/vZwh3iT.png) # Usecases Think of wanting to build a robust dApp showcasing NFT collections, collectibles with their images, tokenId and ownership transfer. Your dApp will be able to do activities such as aggregation, search, associations, and pro filtering which are all examples of complex real-world queries that can be done with your dApp when it calls the subgraph you build. Now, let us proceed with our subgraph. # Create your subgraph on hosted service Go to the hosted service interface online and create a subgraph. Head over to https://thegraph.com/hosted-service/subgraph to create one ![](https://i.imgur.com/neAzKkV.png) # Getting your contracts on OpenSea, installing graph cli and setting up a template md file Let’s begin by finding two projects on opensea. We will be using these two Starlink PixelNauts and Party Degenerates as they follow the ERC-271 standard. Now, create your project. Navigate to the folder you want your subgraph to be, in this case it will be desktop `cd desktop` Before you proceed, keep in mind that the Graph CLI is built in JavaScript and you must install either yarn or npm. Once yarn or npm is installed, install the Graph CLI by running **Yarn** `yarn global add @graphprotocol/graph-cli` **Npm** `npm install -g @graphprotocol/graph-cli` Once the Graph CLI is installed, proceed to run the following command on your terminal ``` graph init --from-contract 0x27cC171B16FdD2224E1f93eD7DccFcc6790cA4BF ``` Here are the snippets of what you will see after running the command Diagrams Select your network ![](https://i.imgur.com/ePpDr6u.png) Choose the product to initialize ![](https://i.imgur.com/DTjXP2R.png) Provide your subgraph name ![](https://i.imgur.com/H5LS6A1.png) Pick the directory ![](https://i.imgur.com/XT7bZmU.png) Choose your network ![](https://i.imgur.com/zVrxeGN.png) Name your first contract i.e the first ERC721 contract ![](https://i.imgur.com/zyC5sEv.png) Wait fot your subgraph scaffold ![](https://i.imgur.com/H1aJAuX.png) Notice you are asked if you want to add another datasource i.e contract Select yes and continue with the contract address ![](https://i.imgur.com/a49Kr0e.png) It will ask if you want to provide a local ABI path, in this case, we want it to generate from etherscan so we select **NO** ![](https://i.imgur.com/8mpvVu7.png) At this point since we don’t have any more contracts to work with, our subgraphs are created and we can go ahead to start defining our schemas and subgraph.yaml. Navigate to the project and optionally you can create a file called `templatefile.md` `touch templatefile.md` The templatefile.md is just a file created to take notes for documentation and organization purposes. Put in the notes information such as the contract name, address, open sea link, schema and whatever information that you think is useful for you. In the schema of thia tutorial we are tracking information about the two collections alongside their collectibles. Here’s how the templatefile.md looks like ``` Collection Name 1- Starlink PixelNauts OpenSea: https://opensea.io/collection/starlink-pixelnauts Contract Address - 0x27cC171B16FdD2224E1f93eD7DccFcc6790cA4BF Collection Name 2 - Party Degenerates OpenSea: https://opensea.io/collection/partydegenerates Contract Address - 0x4BE3223f8708Ca6b30D1E8b8926cF281EC83E770 Schema - type Account @entity { id: ID! address: Bytes! } type Collection @entity { id: ID! name: String symbol: String address: Bytes! } type Collectible @entity { id: ID! tokenId: BigInt! creator: Account! owner: Account! collection: Collection! descriptorUri: String! created: BigInt! modified: BigInt! } type Ownership @entity { id: ID! previousOwner: Bytes! # address newOwner: Bytes! # address } ``` Now that we have a bit of organization and context, let’s go over to the files we have to work with. You will notice that graph init command created the folders as illustrated below for you. ![](https://i.imgur.com/xCMbcWl.png) Firstly, you need to confirm that your generated contract abis are correct. So go to etherscan and search for the contract’s abi. This is what you will find You should check that the abi code generated matches with the one on etherscan, if different, replace the abi code generated with the one on etherscan. ![](https://i.imgur.com/qIpLfWx.png) Head over to your subgraph.yaml file . Firstly, we can see that the two datasources were added Review the subgraph.yaml to adjust necessary changes for example notice that for the second contract, the entities are a bit wrongly renamed so that has to be corrected. ![](https://i.imgur.com/7s9b43x.png) PartyApproval is the wrong entity, the right one should be Approval as defined in the contract abi. That applies to the rest of the entities beneath. Also the abi event handler should get matched properly. E.g handlePartyApproval can be renamed to handleApproval. This might not be the case all the time but it’s also good to double check these values. Another thing missing is our startBlock. You should get the startBlock and add it under the source keyword. # Writing your schema Now that your subgraph.yaml is completed. Head over to your schema.graphql, this is where our templatefile.md comes in handy, so we just go there to pick the schema which we defined, copy and paste into the schema.graphql. We can then proceed to run the command below graph codegen Here is an image of what you get after ![](https://i.imgur.com/hQQtBIJ.png) # Writing your mappings.ts, using helpers for code organization The next place we go to now is our source files src files to fix our mapping files. We already have two files code generated for us that we need to edit. These two files are serving as our mapping files. ![](https://i.imgur.com/iOcQ7xC.png) Let’s start with starlink.ts. We want to deal with only an event handlert, which is the handleTransfer event. You can delete the remaining generated events handlers. Also replace the ExampleEntity with the Entities you defined in your schema i.e Account, Collection and Ownership. We will be writing the handleTransfer function in a bit, within the handleTransfer function, we would need 3 functions which are: getOrCreateAccount, getOrCreateCollection and getOrCreateCollectible. The functions are set up as follows. ```solidity export function getOrCreateAccount(address: Address): Account { let accountAddress = address.toHexString(); let account = Account.load(accountAddress); if (account == null) { account = new Account(accountAddress); account.address = address; account.save(); } return account as Account; } export function getOrCreateCollection(address: Address): Collection { let collectionId = address.toHexString(); let collection = Collection.load(collectionId); if (!collection) { collection = new Collection(collectionId); collection.address = address; let contract = Starlink.bind(address); let nameResult = contract.try_name(); if (!nameResult.reverted) { collection.name = nameResult.value; } let symbolResult = contract.try_symbol(); if (!symbolResult.reverted) { collection.symbol = symbolResult.value; } collection.save(); } return collection; } export function getOrCreateCollectible( collectionAddress: Bytes, collectionId: string, tokenId: BigInt, creatorId: string, createdTimestamp: BigInt ): Collectible { let collectibleId = collectionAddress .toHexString() .concat("-") .concat(tokenId.toHexString()); let collectible = Collectible.load(collectibleId); if (!collectible) { collectible = new Collectible(collectibleId); collectible.tokenId = tokenId; collectible.collection = collectionId; collectible.creator = creatorId; collectible.owner = creatorId; collectible.created = createdTimestamp; collectible.descriptorUri = Starlink.bind( Address.fromBytes(collectionAddress) ).tokenURI(tokenId); collectible.save(); } return collectible; } ``` They would be called in the `handleTransfer` function this way: ```solidity export function handleTransfer(event: Transfer): void { let collection = getOrCreateCollection(event.address); let receiver = getOrCreateAccount(event.params.to); if ( event.params.from == Address.fromString( "0x000000000000000000000000c8a6f408146260e6cc8e5e9919f4ca211d277400" ) ) { // GET COLLECTIBLES OR CREATE ONE IF NON EXISTENT getOrCreateCollectible( collection.address, collection.id, event.params.tokenId, receiver.id, event.block.timestamp ); } else { let collectibleId = collection.address .toHexString() .concat("-") .concat(event.params.tokenId.toHexString()); let collectible = Collectible.load(collectibleId); if (collectible) { if ( event.params.to == Address.fromString("0x0000000000000000000000000000000000000000") ) { let sender = getOrCreateAccount(event.params.from); collectible.owner = sender.id; collectible.created = event.block.timestamp; } else { let sender = getOrCreateAccount(event.params.from); collectible.owner = sender.id; collectible.modified = event.block.timestamp; } collectible.save(); } } } ``` > Note: your `from` address is gotten from the `topic hash` of your contract in the `Transfer` event You should repeat the same process for the second NFT collection. It is always good to keep your code neat hence the need for our helper folder. So, create a helper folder and a helper file for each collection and move getOrCreateAccount, getOrCreateCollection and getOrCreateCollectible. Your folder structure in the end can look like this. ![](https://i.imgur.com/AW1xMjU.png) # Deploying your subgraph Go back to your created subgraph on hosted service , under deploy, click on show commands to run the commands needed for deployment. ![](https://i.imgur.com/q50SUDP.png) ![](https://i.imgur.com/U2FRjNT.png) Run the command for authorization, your access token is in your dashboard `graph auth --product hosted-service <ACCESS_TOKEN>.` If successful, you will see the image below ![](https://i.imgur.com/lWatSxc.png) Now go on to run the command for deployment `graph deploy --product hosted-service <GITHUB_USER>/<SUBGRAPH NAME>` After successful deployment, here is what you see ![](https://i.imgur.com/m85I5w1.png) Congratulations, you have just deployed your subgraph and waiting till it gets synced. You can also go ahead to your subgraph endpoints and run this query ``` { collections(first: 2) { id name symbol address } } ``` You should get the result of the two NFT collections ![](https://i.imgur.com/jK9gpBH.png) To get the first 3 collectibles under Starlink PixelNauts, you can run this query ``` { collections(first: 1) { id name symbol address collectibles(first: 3){ id tokenId } } } ``` You should get the result of the first three collectibles under Starlink PixelNauts like this ![](https://i.imgur.com/z6fXsP2.png) Here's the github link to this [subgraph](https://github.com/amoweolubusayo/duo-nft-subgraph) **Push your project to Radicle** If you are new to radicle, run the following command to create a profile and identity `rad auth` ![](https://i.imgur.com/UPspEmv.png) ***-please skip this process if you already have an identity*** Initialize your project by running `rad init` Provide the name, description and the default branch of your project ![](https://i.imgur.com/0KwwJw2.png) Publish your project and select a seed node by running `rad push ` ![](https://i.imgur.com/JusO6nq.png) This project is available here at **Radicle** https://app.radicle.network/seeds/maple.radicle.garden/rad:git:hnrkpyw8j3e8es4k9k9ddcfp3ophfkgzx6t5y **Github** https://github.com/amoweolubusayo/duo-nft-subgraph![](https://i.imgur.com/W0LjLM1.png)

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully