Skip to main content

Intro

This guide shows you how to index and query USDT0 on Stable using Ormi’s 0xGraph. By the end, you’ll have a live subgraph that tracks transfers, mints, burns and related events, and you’ll be able to query USDT0 activity with GraphQL.

Prerequisites

1. Create and configure your subgraph project

1.1 Get your Ormi API key

  1. Log in to Ormi and go to your dashboard.
  2. From the dashboard, create an API key for deployments.
Create an API key
Keep this page open, we will need the API key to deploy a subgraph.

1.2 Install Graph CLI

On your local machine, run the following in your terminal:
npm install -g @graphprotocol/graph-cli

1.3 Create a directory for your subgraph

mkdir usdt0-stable
cd usdt0-stable
We will call the folder usdt0-stable in this example.

1.4 Initialize the project

Run bash in the directory folder
npm init -y

1.5 Install dependencies

npm install --save-dev @graphprotocol/graph-ts
npm install --save-dev assemblyscript

1.6 Create the required files

You will need:
  • subgraph.yaml
  • schema.graphql
  • mappings/USDT0.ts
  • abis/USDT0.json
After creating these files, put them in the usdt0-stable folder.

Directory layout

usdt0-stable/
├─ abis/
│  └─ USDT0.json

├─ mappings/
│  └─ USDT0.ts

├─ schema.graphql

├─ subgraph.yaml

├─ generated/
│  ├─ schema.ts
│  └─ USDT0/
│     └─ USDT0.ts

├─ build/
│  └─ USDT0/
│     └─ USDT0.wasm

├─ node_modules/
You will also see node_modules/ after installing dependencies - that is expected.

1.7 subgraph.yaml

specVersion: 1.2.0
schema:
  file: ./schema.graphql

dataSources:
  - kind: ethereum
    name: USDT0
    network: stable
    source:
      address: "0x779Ded0c9e1022225f8E0630b35a9b54bE713736"
      abi: USDT0
      startBlock: 2443970
    mapping:
      kind: ethereum/events
      apiVersion: 0.0.7
      language: wasm/assemblyscript
      entities:
        - Token
        - Account
        - Transfer
        - Mint
        - Burn
        - BlocklistChange
        - Authorization
      abis:
        - name: USDT0
          file: ./abis/USDT0.json
      eventHandlers:
        - event: Transfer(indexed address,indexed address,uint256)
          handler: handleTransfer
        - event: Mint(indexed address,uint256)
          handler: handleMint
        - event: Burn(indexed address,uint256)
          handler: handleBurn
        - event: CrosschainMint(indexed address,uint256,indexed address)
          handler: handleCrosschainMint
        - event: CrosschainBurn(indexed address,uint256,indexed address)
          handler: handleCrosschainBurn
        - event: BlockPlaced(indexed address)
          handler: handleBlockPlaced
        - event: BlockReleased(indexed address)
          handler: handleBlockReleased
        - event: DestroyedBlockedFunds(indexed address,uint256)
          handler: handleDestroyedBlockedFunds
        - event: AuthorizationUsed(indexed address,indexed bytes32)
          handler: handleAuthorizationUsed
        - event: AuthorizationCanceled(indexed address,indexed bytes32)
          handler: handleAuthorizationCanceled
        - event: LogUpdateNameAndSymbol(string,string)
          handler: handleLogUpdateNameAndSymbol
        - event: OwnershipTransferred(indexed address,indexed address)
          handler: handleOwnershipTransferred
        - event: LogSetOFTContract(indexed address)
          handler: handleLogSetOFTContract
      file: ./mappings/USDT0.ts

1.8 schema.graphql

type Token @entity(immutable: false) {
  id: ID! # contract address
  name: String!
  symbol: String!
  decimals: Int!
  totalSupply: BigInt! # derived from Mint/Burn/Crosschain*/DestroyedBlockedFunds
  createdAtBlock: BigInt!
  createdAtTimestamp: BigInt!

  transferCount: BigInt!
  mintCount: BigInt!
  burnCount: BigInt!
  crosschainMintCount: BigInt!
  crosschainBurnCount: BigInt!

  owner: Account
  oftContract: Bytes
}

type Account @entity(immutable: false) {
  id: ID! # address
  balance: BigInt! # derived solely from events
  isBlocked: Boolean! # derived from BlockPlaced/BlockReleased
  createdAtBlock: BigInt!
  createdAtTimestamp: BigInt!
  transferCount: BigInt!
}

type Transfer @entity(immutable: true) {
  id: ID! # txHash-logIndex
  token: Token!
  from: Account
  to: Account
  value: BigInt!
  txHash: Bytes!
  blockNumber: BigInt!
  timestamp: BigInt!
}

type Mint @entity(immutable: true) {
  id: ID!
  token: Token!
  to: Account!
  amount: BigInt!
  isCrosschain: Boolean!
  sender: Account
  txHash: Bytes!
  blockNumber: BigInt!
  timestamp: BigInt!
}

type Burn @entity(immutable: true) {
  id: ID!
  token: Token!
  from: Account
  amount: BigInt!
  isCrosschain: Boolean!
  sender: Account
  txHash: Bytes!
  blockNumber: BigInt!
  timestamp: BigInt!
}

type BlocklistChange @entity(immutable: true) {
  id: ID!
  user: Account!
  isBlocked: Boolean!
  destroyedBalance: BigInt
  txHash: Bytes!
  blockNumber: BigInt!
  timestamp: BigInt!
}

type Authorization @entity(immutable: false) {
  id: ID! # `${authorizer}-${nonce}`
  authorizer: Account!
  nonce: Bytes!
  used: Boolean!
  canceled: Boolean!
  txHashUsed: Bytes
  txHashCanceled: Bytes
}

1.9 Mapping files in typescript

// mappings/USDT0.ts

import { BigInt, Address, Bytes } from "@graphprotocol/graph-ts";

import {
  Transfer as TransferEvent,
  Mint as MintEvent,
  Burn as BurnEvent,
  CrosschainMint as CrosschainMintEvent,
  CrosschainBurn as CrosschainBurnEvent,
  BlockPlaced,
  BlockReleased,
  DestroyedBlockedFunds,
  AuthorizationUsed,
  AuthorizationCanceled,
  LogUpdateNameAndSymbol,
  OwnershipTransferred,
  LogSetOFTContract,
} from "../generated/USDT0/USDT0";

import { Token, Account, Transfer, Mint, Burn, BlocklistChange, Authorization } from "../generated/schema";

const USDT0_ADDRESS = "0x779Ded0c9e1022225f8E0630b35a9b54bE713736";
const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";

// Helpers

function createEventId(txHash: Bytes, logIndex: BigInt): string {
  return txHash.toHex() + "-" + logIndex.toString();
}

function getToken(eventBlockNumber: BigInt, eventTimestamp: BigInt): Token {
  let token = Token.load(USDT0_ADDRESS);
  if (token == null) {
    token = new Token(USDT0_ADDRESS);
    token.name = "USDT0";
    token.symbol = "USDT0";
    token.decimals = 6;
    token.totalSupply = BigInt.zero();
    token.createdAtBlock = eventBlockNumber;
    token.createdAtTimestamp = eventTimestamp;
    token.transferCount = BigInt.zero();
    token.mintCount = BigInt.zero();
    token.burnCount = BigInt.zero();
    token.crosschainMintCount = BigInt.zero();
    token.crosschainBurnCount = BigInt.zero();
  }
  return token as Token;
}

function getAccount(addr: Address, blockNumber: BigInt, timestamp: BigInt): Account {
  let id = addr.toHex();
  let account = Account.load(id);
  if (account == null) {
    account = new Account(id);
    account.balance = BigInt.zero();
    account.isBlocked = false;
    account.createdAtBlock = blockNumber;
    account.createdAtTimestamp = timestamp;
    account.transferCount = BigInt.zero();
  }
  return account as Account;
}

// Handlers

export function handleTransfer(event: TransferEvent): void {
  let token = getToken(event.block.number, event.block.timestamp);
  let from = getAccount(event.params.from, event.block.number, event.block.timestamp);
  let to = getAccount(event.params.to, event.block.number, event.block.timestamp);

  let value = event.params.value;

  // compare strings directly instead of using .equals()
  if (from.id != ZERO_ADDRESS) {
    from.balance = from.balance.minus(value);
    from.transferCount = from.transferCount.plus(BigInt.fromI32(1));
    from.save();
  }

  if (to.id != ZERO_ADDRESS) {
    to.balance = to.balance.plus(value);
    to.transferCount = to.transferCount.plus(BigInt.fromI32(1));
    to.save();
  }

  token.transferCount = token.transferCount.plus(BigInt.fromI32(1));
  token.save();

  let transfer = new Transfer(createEventId(event.transaction.hash, event.logIndex));
  transfer.token = token.id;
  transfer.from = from.id;
  transfer.to = to.id;
  transfer.value = value;
  transfer.txHash = event.transaction.hash;
  transfer.blockNumber = event.block.number;
  transfer.timestamp = event.block.timestamp;
  transfer.save();
}

export function handleMint(event: MintEvent): void {
  let token = getToken(event.block.number, event.block.timestamp);
  let to = getAccount(event.params._destination, event.block.number, event.block.timestamp);
  let amount = event.params._amount;

  to.balance = to.balance.plus(amount);
  to.save();

  token.totalSupply = token.totalSupply.plus(amount);
  token.mintCount = token.mintCount.plus(BigInt.fromI32(1));
  token.save();

  let mint = new Mint(createEventId(event.transaction.hash, event.logIndex));
  mint.token = token.id;
  mint.to = to.id;
  mint.amount = amount;
  mint.isCrosschain = false;
  mint.sender = null;
  mint.txHash = event.transaction.hash;
  mint.blockNumber = event.block.number;
  mint.timestamp = event.block.timestamp;
  mint.save();
}

export function handleBurn(event: BurnEvent): void {
  let token = getToken(event.block.number, event.block.timestamp);
  let from = getAccount(event.params.from, event.block.number, event.block.timestamp);
  let amount = event.params.amount;

  from.balance = from.balance.minus(amount);
  from.save();

  token.totalSupply = token.totalSupply.minus(amount);
  token.burnCount = token.burnCount.plus(BigInt.fromI32(1));
  token.save();

  let burn = new Burn(createEventId(event.transaction.hash, event.logIndex));
  burn.token = token.id;
  burn.from = from.id;
  burn.amount = amount;
  burn.isCrosschain = false;
  burn.sender = null;
  burn.txHash = event.transaction.hash;
  burn.blockNumber = event.block.number;
  burn.timestamp = event.block.timestamp;
  burn.save();
}

export function handleCrosschainMint(event: CrosschainMintEvent): void {
  let token = getToken(event.block.number, event.block.timestamp);
  let to = getAccount(event.params.to, event.block.number, event.block.timestamp);
  let sender = getAccount(event.params.sender, event.block.number, event.block.timestamp);
  let amount = event.params.amount;

  to.balance = to.balance.plus(amount);
  to.save();

  token.totalSupply = token.totalSupply.plus(amount);
  token.crosschainMintCount = token.crosschainMintCount.plus(BigInt.fromI32(1));
  token.save();

  let mint = new Mint(createEventId(event.transaction.hash, event.logIndex));
  mint.token = token.id;
  mint.to = to.id;
  mint.amount = amount;
  mint.isCrosschain = true;
  mint.sender = sender.id;
  mint.txHash = event.transaction.hash;
  mint.blockNumber = event.block.number;
  mint.timestamp = event.block.timestamp;
  mint.save();
}

export function handleCrosschainBurn(event: CrosschainBurnEvent): void {
  let token = getToken(event.block.number, event.block.timestamp);
  let from = getAccount(event.params.from, event.block.number, event.block.timestamp);
  let sender = getAccount(event.params.sender, event.block.number, event.block.timestamp);
  let amount = event.params.amount;

  from.balance = from.balance.minus(amount);
  from.save();

  token.totalSupply = token.totalSupply.minus(amount);
  token.crosschainBurnCount = token.crosschainBurnCount.plus(BigInt.fromI32(1));
  token.save();

  let burn = new Burn(createEventId(event.transaction.hash, event.logIndex));
  burn.token = token.id;
  burn.from = from.id;
  burn.amount = amount;
  burn.isCrosschain = true;
  burn.sender = sender.id;
  burn.txHash = event.transaction.hash;
  burn.blockNumber = event.block.number;
  burn.timestamp = event.block.timestamp;
  burn.save();
}

export function handleBlockPlaced(event: BlockPlaced): void {
  let user = getAccount(event.params._user, event.block.number, event.block.timestamp);
  user.isBlocked = true;
  user.save();

  let change = new BlocklistChange(createEventId(event.transaction.hash, event.logIndex));
  change.user = user.id;
  change.isBlocked = true;
  change.destroyedBalance = null;
  change.txHash = event.transaction.hash;
  change.blockNumber = event.block.number;
  change.timestamp = event.block.timestamp;
  change.save();
}

export function handleBlockReleased(event: BlockReleased): void {
  let user = getAccount(event.params._user, event.block.number, event.block.timestamp);
  user.isBlocked = false;
  user.save();

  let change = new BlocklistChange(createEventId(event.transaction.hash, event.logIndex));
  change.user = user.id;
  change.isBlocked = false;
  change.destroyedBalance = null;
  change.txHash = event.transaction.hash;
  change.blockNumber = event.block.number;
  change.timestamp = event.block.timestamp;
  change.save();
}

export function handleDestroyedBlockedFunds(event: DestroyedBlockedFunds): void {
  let token = getToken(event.block.number, event.block.timestamp);
  let user = getAccount(event.params._blockedUser, event.block.number, event.block.timestamp);
  let amount = event.params._balance;

  token.totalSupply = token.totalSupply.minus(amount);
  token.save();

  user.balance = BigInt.zero();
  user.save();

  let change = new BlocklistChange(createEventId(event.transaction.hash, event.logIndex));
  change.user = user.id;
  change.isBlocked = user.isBlocked;
  change.destroyedBalance = amount;
  change.txHash = event.transaction.hash;
  change.blockNumber = event.block.number;
  change.timestamp = event.block.timestamp;
  change.save();
}

export function handleAuthorizationUsed(event: AuthorizationUsed): void {
  let authorizer = getAccount(event.params.authorizer, event.block.number, event.block.timestamp);
  let id = authorizer.id + "-" + event.params.nonce.toHex();
  let auth = Authorization.load(id);
  if (auth == null) {
    auth = new Authorization(id);
    auth.authorizer = authorizer.id;
    auth.nonce = event.params.nonce;
    auth.used = false;
    auth.canceled = false;
  }
  auth.used = true;
  auth.txHashUsed = event.transaction.hash;
  auth.save();
}

export function handleAuthorizationCanceled(event: AuthorizationCanceled): void {
  let authorizer = getAccount(event.params.authorizer, event.block.number, event.block.timestamp);
  let id = authorizer.id + "-" + event.params.nonce.toHex();
  let auth = Authorization.load(id);
  if (auth == null) {
    auth = new Authorization(id);
    auth.authorizer = authorizer.id;
    auth.nonce = event.params.nonce;
    auth.used = false;
    auth.canceled = false;
  }
  auth.canceled = true;
  auth.txHashCanceled = event.transaction.hash;
  auth.save();
}

export function handleLogUpdateNameAndSymbol(event: LogUpdateNameAndSymbol): void {
  let token = getToken(event.block.number, event.block.timestamp);
  token.name = event.params.name;
  token.symbol = event.params.symbol;
  token.save();
}

export function handleOwnershipTransferred(event: OwnershipTransferred): void {
  let token = getToken(event.block.number, event.block.timestamp);
  let newOwner = getAccount(event.params.newOwner, event.block.number, event.block.timestamp);
  token.owner = newOwner.id;
  token.save();
}

export function handleLogSetOFTContract(event: LogSetOFTContract): void {
  let token = getToken(event.block.number, event.block.timestamp);
  token.oftContract = event.params.oftContract;
  token.save();
}

1.9 ABIs

[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"authorizer","type":"address"},{"indexed":true,"internalType":"bytes32","name":"nonce","type":"bytes32"}],"name":"AuthorizationCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"authorizer","type":"address"},{"indexed":true,"internalType":"bytes32","name":"nonce","type":"bytes32"}],"name":"AuthorizationUsed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_user","type":"address"}],"name":"BlockPlaced","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_user","type":"address"}],"name":"BlockReleased","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Burn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"CrosschainBurn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"CrosschainMint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_blockedUser","type":"address"},{"indexed":false,"internalType":"uint256","name":"_balance","type":"uint256"}],"name":"DestroyedBlockedFunds","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oftContract","type":"address"}],"name":"LogSetOFTContract","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"name","type":"string"},{"indexed":false,"internalType":"string","name":"symbol","type":"string"}],"name":"LogUpdateNameAndSymbol","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_destination","type":"address"},{"indexed":false,"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"Redeem","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"CANCEL_AUTHORIZATION_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RECEIVE_WITH_AUTHORIZATION_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TRANSFER_WITH_AUTHORIZATION_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"}],"name":"addToBlockedList","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"authorizer","type":"address"},{"internalType":"bytes32","name":"nonce","type":"bytes32"}],"name":"authorizationState","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"authorizer","type":"address"},{"internalType":"bytes32","name":"nonce","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"cancelAuthorization","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"authorizer","type":"address"},{"internalType":"bytes32","name":"nonce","type":"bytes32"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"cancelAuthorization","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"crosschainBurn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_destination","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"crosschainMint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_blockedUser","type":"address"}],"name":"destroyBlockedFunds","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"},{"internalType":"uint8","name":"_decimals","type":"uint8"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"isBlocked","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"isTrusted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_destination","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_recipients","type":"address[]"},{"internalType":"uint256[]","name":"_values","type":"uint256[]"}],"name":"multiTransfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oftContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner_","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner_","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"validAfter","type":"uint256"},{"internalType":"uint256","name":"validBefore","type":"uint256"},{"internalType":"bytes32","name":"nonce","type":"bytes32"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"receiveWithAuthorization","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"validAfter","type":"uint256"},{"internalType":"uint256","name":"validBefore","type":"uint256"},{"internalType":"bytes32","name":"nonce","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"receiveWithAuthorization","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"redeem","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"}],"name":"removeFromBlockedList","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_oftContract","type":"address"}],"name":"setOFTContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_sender","type":"address"},{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"validAfter","type":"uint256"},{"internalType":"uint256","name":"validBefore","type":"uint256"},{"internalType":"bytes32","name":"nonce","type":"bytes32"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"transferWithAuthorization","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"validAfter","type":"uint256"},{"internalType":"uint256","name":"validBefore","type":"uint256"},{"internalType":"bytes32","name":"nonce","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"transferWithAuthorization","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"}],"name":"updateNameAndSymbol","outputs":[],"stateMutability":"nonpayable","type":"function"}]

2. Build the Subgraph

Generate types:
graph codegen
Run codegen
Now run:
graph build
Build graph

3. Deploy the Subgraph to Ormi

Return to your API key from the dashboard. Replace <graph-name> and <API key> with your actual values
graph deploy <graph-name> --node  https://subgraph.api.ormilabs.com/deploy --ipfs https://subgraph.api.ormilabs.com/ipfs --deploy-key <API key>
Deploy graph

Track sync status

You can check syncing in the dashboard by going to the Subgraphs tab:
Track sync status

4. Query USDT0

Click on the button where the red arrow is pointing.
Click on graphql icon
Once synced, click the GraphQL endpoint link in the dashboard.
Graphql link

Sample query

Run this query to fetch the latest transfers
{
  transfers(first: 10, orderBy: timestamp, orderDirection: desc) {
    id
    from {
      id
    }
    to {
      id
    }
    value
    blockNumber
    timestamp
    txHash
  }
}
Query graphql

Done!

You now have a live subgraph indexing USDT0 on Stable via Ormi’s 0xGraph. Want the full walkthrough with context? Check out the USDT0 Subgraph Tutorial on the Ormi Blog.