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
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
Graph CLI: Install the Graph CLI globally using npm:
npminstall-g@graphprotocol/graph-cli
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.
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:
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.
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()
}