Using TheGraph for Indexing
The Graph Protocol is a decentralized indexing protocol that enables efficient querying of blockchain data. In Rahat, we use The Graph to index blockchain events and provide fast, reliable access to on-chain data through GraphQL APIs.
Overview
What is The Graph?
The Graph is an indexing protocol for querying networks like Ethereum and IPFS. Anyone can build and publish open APIs, called subgraphs, making data easily accessible.
Why Use Subgraphs in Rahat?
- Real-time Data: Index blockchain events as they happen
- Efficient Queries: GraphQL API for complex data queries
- Decentralized: No single point of failure
- Cost-effective: Reduce RPC calls and gas costs
- Scalable: Handle high-volume blockchain data
Architecture
Rahat Subgraph Components
Rahat Platform
├── Smart Contracts (EVM/Stellar)
├── The Graph Subgraph
│ ├── Schema Definition
│ ├── Event Handlers
│ ├── Entity Mappings
│ └── GraphQL API
└── Frontend Applications
Indexed Events
The Rahat subgraph indexes the following blockchain events:
Token Events
Transfer
- ERC-20 token transfersApproval
- Token approval eventsMint
- Token minting operationsBurn
- Token burning operations
Beneficiary Events
BeneficiaryAdded
- New beneficiary registrationBeneficiaryRemoved
- Beneficiary removalBeneficiaryUpdated
- Beneficiary information updates
Project Events
ProjectCreated
- New project initializationTokensAllocated
- Token budget allocationTokensDisbursed
- Token distribution to beneficiariesCampaignCreated
- Aid campaign creation
Claim Events
ClaimCreated
- New claim submissionClaimProcessed
- Claim processing completionOtpVerified
- OTP verification eventsOfflineClaimProcessed
- Offline claim processing
Vendor Events
VendorRegistered
- Vendor registrationRedemptionProcessed
- Token redemption events
Setup and Installation
Prerequisites
- Node.js (v18 or higher)
- Docker (for local Graph Node)
- Graph CLI - Install globally:
npm install -g @graphprotocol/graph-cli
Environment Configuration
Create a .env
file in your project root:
# Database
DATABASE_URL="postgresql://rahat:rahat@localhost:5432/rahat-rp"
# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
# Blockchain
PRIVATE_KEY=your_private_key
RPC_URL=your_rpc_url
# The Graph
GRAPH_NODE_URL=http://localhost:8020
IPFS_URL=http://localhost:5001
GRAPH_STUDIO_KEY=your_graph_studio_key
Project Structure
apps/
└── graph/ # The Graph subgraph
├── src/
│ ├── mappings/ # Event handlers
│ └── schema.graphql # GraphQL schema
├── subgraph.yaml # Subgraph manifest
└── package.json
Subgraph Development
1. Schema Definition
Define your GraphQL schema in schema.graphql
:
type Beneficiary @entity {
id: ID!
address: Bytes!
name: String
phone: String
type: BeneficiaryType!
createdAt: BigInt!
updatedAt: BigInt!
claims: [Claim!]! @derivedFrom(field: "beneficiary")
transfers: [Transfer!]! @derivedFrom(field: "to")
}
type Project @entity {
id: ID!
name: String!
token: Token!
totalAllocated: BigInt!
totalDisbursed: BigInt!
beneficiaries: [Beneficiary!]!
campaigns: [Campaign!]!
createdAt: BigInt!
}
type Token @entity {
id: ID!
address: Bytes!
name: String!
symbol: String!
decimals: Int!
totalSupply: BigInt!
transfers: [Transfer!]! @derivedFrom(field: "token")
}
type Transfer @entity {
id: ID!
token: Token!
from: Beneficiary
to: Beneficiary!
amount: BigInt!
timestamp: BigInt!
transactionHash: String!
}
type Claim @entity {
id: ID!
beneficiary: Beneficiary!
amount: BigInt!
status: ClaimStatus!
otpVerified: Boolean!
createdAt: BigInt!
processedAt: BigInt
}
enum BeneficiaryType {
ENROLLED
REFERRED
}
enum ClaimStatus {
PENDING
PROCESSED
REJECTED
}
2. Event Handlers
Create mapping functions in src/mappings/
:
// src/mappings/beneficiary.ts
import { BeneficiaryAdded, BeneficiaryRemoved } from '../generated/RahatProject/RahatProject'
import { Beneficiary } from '../generated/schema'
export function handleBeneficiaryAdded(event: BeneficiaryAdded): void {
let beneficiary = new Beneficiary(event.params.beneficiary.toHexString())
beneficiary.address = event.params.beneficiary
beneficiary.name = event.params.name
beneficiary.phone = event.params.phone
beneficiary.type = event.params.beneficiaryType
beneficiary.createdAt = event.block.timestamp
beneficiary.updatedAt = event.block.timestamp
beneficiary.save()
}
export function handleBeneficiaryRemoved(event: BeneficiaryRemoved): void {
let beneficiary = Beneficiary.load(event.params.beneficiary.toHexString())
if (beneficiary) {
beneficiary.updatedAt = event.block.timestamp
beneficiary.save()
}
}
// src/mappings/token.ts
import { Transfer } from '../generated/RahatToken/RahatToken'
import { Transfer as TransferEntity, Token, Beneficiary } from '../generated/schema'
export function handleTransfer(event: Transfer): void {
let transfer = new TransferEntity(event.transaction.hash.toHexString())
transfer.token = event.address.toHexString()
transfer.from = event.params.from.toHexString()
transfer.to = event.params.to.toHexString()
transfer.amount = event.params.value
transfer.timestamp = event.block.timestamp
transfer.transactionHash = event.transaction.hash.toHexString()
transfer.save()
}
3. Subgraph Manifest
Configure your subgraph in subgraph.yaml
:
specVersion: 0.0.4
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum
name: RahatProject
network: mainnet
source:
address: "0x..."
abi: RahatProject
startBlock: 12345678
mapping:
kind: ethereum/events
apiVersion: 0.0.6
language: wasm/assemblyscript
entities:
- Beneficiary
- Project
- Claim
abis:
- name: RahatProject
file: ./abis/RahatProject.json
eventHandlers:
- event: BeneficiaryAdded(indexed address,string,string,uint8)
handler: handleBeneficiaryAdded
- event: BeneficiaryRemoved(indexed address)
handler: handleBeneficiaryRemoved
- event: ClaimCreated(indexed address,uint256)
handler: handleClaimCreated
file: ./src/mappings/beneficiary.ts
- kind: ethereum
name: RahatToken
network: mainnet
source:
address: "0x..."
abi: RahatToken
startBlock: 12345678
mapping:
kind: ethereum/events
apiVersion: 0.0.6
language: wasm/assemblyscript
entities:
- Token
- Transfer
abis:
- name: RahatToken
file: ./abis/RahatToken.json
eventHandlers:
- event: Transfer(indexed address,indexed address,uint256)
handler: handleTransfer
file: ./src/mappings/token.ts
Deployment
Local Development
-
Start Local Graph Node:
docker-compose up -d
-
Create Local Subgraph:
graph create --node http://localhost:8020 rahat/local
-
Deploy to Local Node:
graph deploy --node http://localhost:8020 --ipfs http://localhost:5001 rahat/local
Hosted Service Deployment
-
Authenticate with Graph Studio:
graph auth --product hosted-service <ACCESS_TOKEN>
-
Deploy to Hosted Service:
graph deploy --product hosted-service <GITHUB_USERNAME>/<SUBGRAPH_NAME>
Available Scripts
# Generate GraphQL types
pnpm graph:codegen
# Build subgraph
pnpm graph:build
# Deploy to hosted service
pnpm graph:deploy
# Local development
pnpm graph:deploy-local
# Create local subgraph
pnpm graph:create-local
Querying Data
GraphQL Queries
Once deployed, you can query your subgraph using GraphQL:
Get All Beneficiaries
query GetBeneficiaries {
beneficiaries {
id
address
name
phone
type
createdAt
}
}
Get Beneficiary with Claims
query GetBeneficiaryWithClaims($id: ID!) {
beneficiary(id: $id) {
id
name
claims {
id
amount
status
createdAt
}
}
}
Get Token Transfers
query GetTransfers($token: String!) {
transfers(where: { token: $token }) {
id
from {
name
}
to {
name
}
amount
timestamp
}
}
Get Project Statistics
query GetProjectStats($id: ID!) {
project(id: $id) {
id
name
totalAllocated
totalDisbursed
beneficiaries {
id
name
}
}
}
Using in Applications
JavaScript/TypeScript
import { GraphQuery } from '@rahataid/el-subgraph';
const query = `
query GetBeneficiaries {
beneficiaries {
id
name
type
}
}
`;
const result = await GraphQuery(query, {
endpoint: 'YOUR_SUBGRAPH_ENDPOINT',
});
React Hook
import { useQuery } from '@apollo/client';
const GET_BENEFICIARIES = gql`
query GetBeneficiaries {
beneficiaries {
id
name
type
}
}
`;
function BeneficiaryList() {
const { loading, error, data } = useQuery(GET_BENEFICIARIES);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>
{data.beneficiaries.map(beneficiary => (
<li key={beneficiary.id}>{beneficiary.name}</li>
))}
</ul>
);
}
Monitoring and Maintenance
Health Checks
- Subgraph Status: Monitor indexing status in Graph Studio
- Sync Status: Check if subgraph is synced with latest blocks
- Error Logs: Review failed indexing events
- Performance: Monitor query response times
Common Issues
Indexing Failures
- Cause: Invalid event data or mapping errors
- Solution: Check event handlers and entity definitions
Sync Delays
- Cause: High blockchain activity or network issues
- Solution: Monitor block processing and network status
Query Timeouts
- Cause: Complex queries or large datasets
- Solution: Optimize queries and add pagination
Best Practices
- Event Design: Emit events with indexed parameters for efficient querying
- Entity Relationships: Use derived fields to avoid redundant data
- Pagination: Implement cursor-based pagination for large datasets
- Caching: Cache frequently accessed data in your application
- Error Handling: Implement proper error handling for subgraph queries
Integration with Rahat Projects
C2C (Crypto-to-Crypto)
- Indexes token transfers and beneficiary events
- Tracks multi-signature approvals
- Monitors project budget allocations
CVA (Cash Voucher Assistance)
- Indexes claim creation and processing
- Tracks vendor registrations and redemptions
- Monitors token disbursements
AA (Anticipatory Action)
- Indexes trigger events and forecasts
- Tracks automated disbursements
- Monitors communication events
Troubleshooting
Local Development Issues
-
Docker Not Running:
docker-compose up -d
-
Port Conflicts:
- Ensure ports 8000-8005, 8020, 5001 are available
- Check for existing Graph Node instances
-
Authentication Issues:
graph auth --product hosted-service <NEW_TOKEN>
Deployment Issues
- Invalid ABI: Ensure contract ABIs are up-to-date
- Network Mismatch: Verify network configuration in subgraph.yaml
- Start Block: Set appropriate start block for indexing
Query Issues
- Schema Mismatch: Regenerate types after schema changes
- Missing Entities: Ensure all entities are defined in schema
- Invalid Queries: Use GraphQL playground to test queries
Resources
Support
For issues specific to Rahat subgraphs:
- Check the project's GitHub repository
- Review existing issues and pull requests
- Contact the development team
- Join the community Discord/Telegram channels