Deploy a Subgraph

Deploying a subgraph involves creating a GraphQL schema, defining mappings to extract data from a blockchain or data source, and deploying the subgraph to a GraphNode service. In this tutorial, we'll walk you through the process of deploying a subgraph using the Graph CLI.

Prerequisites

  1. Node.js and npm: Make sure you have Node.js and npm installed on your system by checking in Terminal (or you can download them from the Node.js website):

    $ node -v
    v19.9.0
    $ npm -v
    9.6.3
  2. Graph CLI: Install the Graph CLI globally using npm:

    npm install -g @graphprotocol/graph-cli
  3. Access to a graph node: Ensure that you have access to a running graph node service. You have set up your own graph node at here.

We will deploy a subgraph for smart contract MyToken.sol on Solaris mainnet: 0xb4C122037a433aEc9FcB25A415Fe1f91845e3958.

Create new project

Create new Node.js project and install dependencies:

mkdir sample_subgraph
cd sample_subgraph
npm init -y

Let's modify your package.json file to set up Node.js project:

{
  "name": "sample-subgraph",
  "version": "1.0.0",
  "scripts": {
    "codegen": "graph codegen",
    "create": "graph create u2u/subgraph --node http://127.0.0.1:8020",
    "deploy": "graph deploy u2u/subgraph --ipfs http://127.0.0.1:5001 --node http://127.0.0.1:8020"
  },
  "devDependencies": {
    "@amxx/graphprotocol-utils": "^1.1.0",
    "@graphprotocol/graph-cli": "^0.59.0",
    "@graphprotocol/graph-ts": "^0.31.0"
  },
  "dependencies": {}
}

Now install dependencies:

npm install

Add contract ABI

At project directory, create new folder called abis. Then create new file MyToken.json that located inside abis folder:

abis/
    MyToken.json

MyToken.json contains MyToken contract's ABI. You can retrieve ABI of verified contract via explorer (Contract detail page > tab Contract > button Code > Contract ABI) or copy from compiled ABI with hardhat (artifacts folder). It has this format:

{
    "contractName": "MyToken",
    "abi": [ /* copy ABI and paste here */ ]
}

Define schema

A GraphQL schema that defines what data is stored for your subgraph, and how to query it via GraphQL. The schema for your subgraph is in the file schema.graphql. GraphQL schemas are defined using the GraphQL interface definition language. If you've never written a GraphQL schema, it is recommended that you check out this primer on the GraphQL type system. Reference documentation for GraphQL schemas can be found in the GraphQL API section.

At project directory, create new file called schema.graphql and paste following contents:

type Account @entity {
	id: Bytes!
	asERC20: ERC20Contract
	ERC20balances: [ERC20Balance!]! @derivedFrom(field: "account")
	ERC20approvalsOwner: [ERC20Approval!]! @derivedFrom(field: "owner")
	ERC20approvalsSpender: [ERC20Approval!]! @derivedFrom(field: "spender")
	ERC20transferFromEvent: [ERC20Transfer!]! @derivedFrom(field: "from")
	ERC20transferToEvent: [ERC20Transfer!]! @derivedFrom(field: "to")
	events: [Event!]! @derivedFrom(field: "emitter")
}
type ERC20Contract @entity(immutable: true) {
	id: Bytes!
	asAccount: Account!
	name: String
	symbol: String
	decimals: Int!
	totalSupply: ERC20Balance!
	balances: [ERC20Balance!]! @derivedFrom(field: "contract")
	approvals: [ERC20Approval!]! @derivedFrom(field: "contract")
	transfers: [ERC20Transfer!]! @derivedFrom(field: "contract")
}
type ERC20Balance @entity {
	id: ID!
	contract: ERC20Contract!
	account: Account
	value: BigDecimal!
	valueExact: BigInt!
	transferFromEvent: [ERC20Transfer!]! @derivedFrom(field: "fromBalance")
	transferToEvent: [ERC20Transfer!]! @derivedFrom(field: "toBalance")
}
type ERC20Approval @entity {
	id: ID!
	contract: ERC20Contract!
	owner: Account!
	spender: Account!
	value: BigDecimal!
	valueExact: BigInt!
}
type ERC20Transfer implements Event @entity(immutable: true) {
	id: ID!
	emitter: Account!
	transaction: Transaction!
	timestamp: BigInt!
	contract: ERC20Contract!
	from: Account
	fromBalance: ERC20Balance
	to: Account
	toBalance: ERC20Balance
	value: BigDecimal!
	valueExact: BigInt!
}
interface Event {
	id: ID!
	transaction: Transaction!
	emitter: Account!
	timestamp: BigInt!
}
type Transaction @entity(immutable: true) {
	id: ID!
	timestamp: BigInt!
	blockNumber: BigInt!
	events: [Event!]! @derivedFrom(field: "transaction")
}

Learn more about defining entities in your subgraph.

Subgraph manifest

Subgraph manifest is a configuration file for a subgraph. Below is the subgraph manifest for our sample:

specVersion: 0.0.4
description: Sample Subgraph
repository: https://github.com/unicornultrafoundation/subgraph
schema:
  file: ./schema.graphql
dataSources:
  - kind: ethereum/contract
    name: MyToken
    source:
      abi: MyToken
      address: "0xa3Aa4cb3DB300355613cb3388D4eD8823f4ED07A"
      startBlock: 7699806
    mapping:
      kind: ethereum/events
      apiVersion: 0.0.6
      language: wasm/assemblyscript
      entities:
        - TransferEvent
      abis:
        - name: MyToken
          file: ./abis/MyToken.json
      eventHandlers:
        - event: Transfer(indexed address,indexed address,uint256)
          handler: handleTransfer
      file: ./src/mytoken.ts

Let's break down the elements of this subgraph manifest:

  • specVersion: 0.0.4: This specifies the version of the subgraph manifest. It indicates which version of the subgraph schema and configuration format is being used.

  • description: Sample Subgraph: This is a human-readable description of the subgraph, providing information about its purpose or functionality.

  • schema.file: This points to the GraphQL schema file used for defining the data structure and types that the subgraph will query and expose.

  • dataSources: This section defines the data sources that the subgraph will query:

    • kind: Specifies that the data source is smart contract.

    • name: A user-assigned name for the data source.

    • source.abi: The ABI for the smart contract with the name MyToken.

    • source.address: The address of contract MyToken.

    • source.startBlock: block number from which indexing should start.

  • mapping: specifies how the data from the Ethereum smart contract is mapped to the subgraph:

    • kind: ethereum/events: This indicates that the data source is indexed based on events.

    • apiVersion: 0.0.6: The version of the AssemblyScript being used.

    • language: wasm/assemblyscript: The programming language used for writing the mapping logic.

    • entities: the entities that the data source writes to the store. The schema for each entity is defined in the schema.graphql file

    • abis: one or more named ABI files for the source contract as well as any other smart contracts that you interact with from within the mappings.

    • eventHandlers: lists the smart contract events this subgraph reacts to and the handlers in the mapping—./src/mytoken.ts in the example—that transform these events into entities in the store.

Check out this link to learn more about subgraph manifest.

Define mappings

The mappings take data from a particular source and transform it into entities that are defined within your schema. Mappings are written in a subset of TypeScript called AssemblyScript which can be compiled to WASM (WebAssembly). AssemblyScript is stricter than normal TypeScript, yet provides a familiar syntax.

For each event handler that is defined in subgraph.yaml under mapping.eventHandlers, create an exported function of the same name. Each handler must accept a single parameter called eventwith a type corresponding to the name of the event which is being handled.

At project directory, create a new folder src. Add 2 files as below:

src/
    fetch.ts
    mytoken.ts
// fetch.ts
import {
	Address,
} from '@graphprotocol/graph-ts'

import {
	Account,
	ERC20Contract,
	ERC20Balance,
	ERC20Approval,
} from '../generated/schema'

import {
	MyToken,
} from '../generated/MyToken/MyToken'

import {
	constants,
} from '@amxx/graphprotocol-utils'

export function fetchAccount(address: Address): Account {
	let account = new Account(address)
	account.save()
	return account
}

export function fetchERC20(address: Address): ERC20Contract {
	let contract = ERC20Contract.load(address)

	if (contract == null) {
		let endpoint = MyToken.bind(address)
		let name = endpoint.try_name()
		let symbol = endpoint.try_symbol()
		let decimals = endpoint.try_decimals()

		// Common
		contract = new ERC20Contract(address)
		contract.name = name.reverted ? null : name.value
		contract.symbol = symbol.reverted ? null : symbol.value
		contract.decimals = decimals.reverted ? 18 : decimals.value
		contract.totalSupply = fetchERC20Balance(contract as ERC20Contract, null).id
		contract.asAccount = address
		contract.save()

		let account = fetchAccount(address)
		account.asERC20 = address
		account.save()
	}

	return contract as ERC20Contract
}

export function fetchERC20Balance(contract: ERC20Contract, account: Account | null): ERC20Balance {
	let id = contract.id.toHex().concat('/').concat(account ? account.id.toHex() : 'totalSupply')
	let balance = ERC20Balance.load(id)

	if (balance == null) {
		balance = new ERC20Balance(id)
		balance.contract = contract.id
		balance.account = account ? account.id : null
		balance.value = constants.BIGDECIMAL_ZERO
		balance.valueExact = constants.BIGINT_ZERO
		balance.save()
	}

	return balance as ERC20Balance
}

export function fetchERC20Approval(contract: ERC20Contract, owner: Account, spender: Account): ERC20Approval {
	let id = contract.id.toHex().concat('/').concat(owner.id.toHex()).concat('/').concat(spender.id.toHex())
	let approval = ERC20Approval.load(id)

	if (approval == null) {
		approval = new ERC20Approval(id)
		approval.contract = contract.id
		approval.owner = owner.id
		approval.spender = spender.id
		approval.value = constants.BIGDECIMAL_ZERO
		approval.valueExact = constants.BIGINT_ZERO
	}

	return approval as ERC20Approval
}
// mytoken.ts
import {
	Address,
} from '@graphprotocol/graph-ts'

import {
	ERC20Transfer,
} from '../generated/schema'

import {
	Transfer as TransferEvent,
	Approval as ApprovalEvent,
} from '../generated/MyToken/MyToken'

import {
	decimals,
	events,
	transactions,
} from '@amxx/graphprotocol-utils'

import {
	fetchERC20,
	fetchERC20Balance,
	fetchERC20Approval,
	fetchAccount,
} from './fetch'

export function handleTransfer(event: TransferEvent): void {
	let contract = fetchERC20(event.address)
	let ev = new ERC20Transfer(events.id(event))
	ev.emitter = contract.id
	ev.transaction = transactions.log(event).id
	ev.timestamp = event.block.timestamp
	ev.contract = contract.id
	ev.value = decimals.toDecimals(event.params.value, contract.decimals)
	ev.valueExact = event.params.value

	if (event.params.from == Address.zero()) {
		let totalSupply = fetchERC20Balance(contract, null)
		totalSupply.valueExact = totalSupply.valueExact.plus(event.params.value)
		totalSupply.value = decimals.toDecimals(totalSupply.valueExact, contract.decimals)
		totalSupply.save()
	} else {
		let from = fetchAccount(event.params.from)
		let balance = fetchERC20Balance(contract, from)
		balance.valueExact = balance.valueExact.minus(event.params.value)
		balance.value = decimals.toDecimals(balance.valueExact, contract.decimals)
		balance.save()

		ev.from = from.id
		ev.fromBalance = balance.id
	}

	if (event.params.to == Address.zero()) {
		let totalSupply = fetchERC20Balance(contract, null)
		totalSupply.valueExact = totalSupply.valueExact.minus(event.params.value)
		totalSupply.value = decimals.toDecimals(totalSupply.valueExact, contract.decimals)
		totalSupply.save()
	} else {
		let to = fetchAccount(event.params.to)
		let balance = fetchERC20Balance(contract, to)
		balance.valueExact = balance.valueExact.plus(event.params.value)
		balance.value = decimals.toDecimals(balance.valueExact, contract.decimals)
		balance.save()

		ev.to = to.id
		ev.toBalance = balance.id
	}
	ev.save()
}

export function handleApproval(event: ApprovalEvent): void {
	let contract = fetchERC20(event.address)

	let owner = fetchAccount(event.params.owner)
	let spender = fetchAccount(event.params.spender)
	let approval = fetchERC20Approval(contract, owner, spender)
	approval.valueExact = event.params.value
	approval.value = decimals.toDecimals(event.params.value, contract.decimals)
	approval.save()
}

Learn more about writing mappings here.

Codegen, create and deploy subgraph

At the project structure, open up your Terminal, to generate code:

npm run codegen

After finishing, generated folder will be created. It contains AssemblyScript files that defined in GraphQL schema and ABI files:

generated/
    MyToken/
        MyToken.ts
    schema.ts

Then create your subgraph on graphnode:

npm run create

You'll see something similar:

> sample-subgraph@1.0.0 create
> graph create u2u/subgraph --node http://127.0.0.1:8020

Created subgraph: u2u/subgraph

Next deploy your subgraph:

npm run deploy

The output is as below:

> sample-subgraph@1.0.0 deploy
> graph deploy u2u/subgraph --ipfs http://127.0.0.1:5001 --node http://127.0.0.1:8020

Which version label to use? (e.g. "v0.0.1"): v0.0.1
  Skip migration: Bump mapping apiVersion from 0.0.1 to 0.0.2
  Skip migration: Bump mapping apiVersion from 0.0.2 to 0.0.3
  Skip migration: Bump mapping apiVersion from 0.0.3 to 0.0.4
  Skip migration: Bump mapping apiVersion from 0.0.4 to 0.0.5
  Skip migration: Bump mapping apiVersion from 0.0.5 to 0.0.6
  Skip migration: Bump manifest specVersion from 0.0.1 to 0.0.2
  Skip migration: Bump manifest specVersion from 0.0.2 to 0.0.4
✔ Apply migrations
✔ Load subgraph from subgraph.yaml
  Compile data source: MyToken => build/MyToken/MyToken.wasm
✔ Compile subgraph
  Copy schema file build/schema.graphql
  Write subgraph file build/MyToken/abis/MyToken.json
  Write subgraph manifest build/subgraph.yaml
✔ Write compiled subgraph to build/
  Add file to IPFS build/schema.graphql
                .. QmTJC228tSzgV6ano5uSgVkk4yq3tdpUAZ11xBBnqtKbmm
  Add file to IPFS build/MyToken/abis/MyToken.json
                .. QmUfqVeUafPnxQbia1tCA9mCtvcRDAoxLjsCnvioEfK2P4
  Add file to IPFS build/MyToken/MyToken.wasm
                .. QmZjUQmrLP75UqCypjvQSLzfYs35nX5GvNRur3QCjgYq8P
✔ Upload subgraph to IPFS

Build completed: Qmf6snbu1krZwRL3MbjK1xBusxvbWeP39G2Z54WK6WPsfX

Deployed to http://127.0.0.1:8000/subgraphs/name/u2u/subgraph/graphql

Subgraph endpoints:
Queries (HTTP):     http://127.0.0.1:8000/subgraphs/name/u2u/subgraph

Visit your subgraph's GraphiQL

Use your browser to access deployed subgraph: http://127.0.0.1:8000/subgraphs/name/u2u/subgraph/graphql

Summary

We have gone through how to deploy subgraph on graphnode that watches U2U network. For more detail please visit The Graph documentation.

To sum up:

  1. Add contract ABI

  2. Write mappings for contract in /src/*

  3. Define models in schema.graphql

  4. Wire up contract, its events, mappings in subgraph.yaml

  5. Try codegen to see if any errors

  6. Run create then deploy

Last updated