Roles Indexer
Comprehensive real-time indexing for the Roles contract, providing complete audit trails, user role management, and analytics for role-based access control events.
Overview
The Roles indexer processes all role management events from the Roles contract, transforming raw blockchain events into structured, queryable data. It provides real-time tracking of role assignments, ownership changes, and comprehensive analytics for governance transparency.
Features
🔍 Event Processing
- RoleGranted: Real-time role assignment tracking
- RoleRevoked: Role removal with state updates
- OwnershipTransferred: Complete ownership history
- OwnershipTransferStarted: 2-step ownership process support
📊 Data Analytics
- Role holder statistics and metrics
- User role state management
- Historical audit trails
- Ownership governance transparency
🛡️ Data Integrity
- Duplicate event prevention
- Transaction-level consistency
- Type-safe data validation
- Comprehensive error handling
Database Schema
roleActivities Table
Complete audit trail for all role operations.
{
id: Hash, // Ponder's unique event ID (composite primary key with chainId)
chainId: number, // Chain ID for multi-chain support
type: number, // RoleActivityType enum (GRANTED=0, REVOKED=1)
role: Hash, // Role identifier (bytes32)
account: Address, // Target account address
sender: Address, // Transaction initiator
timestamp: bigint, // Block timestamp
blockNumber: bigint // Block number
}- Compliance auditing
- Role assignment history
- Transaction forensics
- Governance reporting
users Table
Current role state for each user address.
{
id: Address, // User wallet address (composite primary key with chainId)
chainId: number, // Chain ID for multi-chain support
roles: Hash[] // Array of currently held roles
}- Real-time permission checks
- User role dashboards
- Access control verification
- Role inheritance tracking
roleStats Table
Statistical analytics for role distribution.
{
id: Hash, // Role identifier (composite primary key with chainId)
chainId: number, // Chain ID for multi-chain support
totalHolders: number, // Current number of role holders
lastUpdated: bigint // Last update timestamp
}- Role distribution analytics
- Governance metrics
- Security monitoring
- Administrative insights
rolesOwnershipHistory Table
Complete ownership transfer history for governance transparency.
{
id: Hash, // Unique ownership change ID (composite primary key with chainId)
chainId: number, // Chain ID for multi-chain support
previousOwner: Address, // Previous contract owner
newOwner: Address, // New contract owner
timestamp: bigint, // Transfer timestamp
blockNumber: bigint // Block number
}- Governance transparency
- Ownership audit trails
- Legal compliance
- Historical analysis
GraphQL API
Core Queries
Get Recent Role Activities
query RecentActivities($limit: Int = 10) {
roleActivities(
limit: $limit,
orderBy: "timestamp",
orderDirection: "desc"
) {
items {
id
type
role
account
sender
timestamp
blockNumber
}
}
}Get User Roles
query UserRoles($address: String!) {
user(id: $address) {
id
roles
}
}Get Role Statistics
query RoleStats($role: String!) {
roleStat(id: $role) {
id
totalHolders
lastUpdated
}
}Get Ownership History
query OwnershipHistory($limit: Int = 20) {
rolesOwnershipHistories(
limit: $limit,
orderBy: "timestamp",
orderDirection: "desc"
) {
items {
id
previousOwner
newOwner
timestamp
blockNumber
}
}
}Advanced Queries
Role Activity by Type
query RoleActivitiesByType($type: RoleActivityType!, $limit: Int = 50) {
roleActivities(
where: { type: $type },
limit: $limit,
orderBy: "timestamp",
orderDirection: "desc"
) {
items {
id
role
account
sender
timestamp
}
}
}Users with Specific Role
query UsersWithRole($role: String!) {
users(where: { roles: { has: $role } }) {
items {
id
roles
}
}
}Recent Role Changes for User
query UserRoleHistory($account: String!, $limit: Int = 10) {
roleActivities(
where: { account: $account },
limit: $limit,
orderBy: "timestamp",
orderDirection: "desc"
) {
items {
id
type
role
sender
timestamp
blockNumber
}
}
}Implementation Examples
TypeScript Integration
import { GraphQLClient } from 'graphql-request'
const client = new GraphQLClient('http://localhost:42069')
// Check if user has specific role
async function hasRole(userAddress: string, roleHash: string): Promise<boolean> {
const query = `
query CheckUserRole($address: String!) {
user(id: $address) {
roles
}
}
`
const response = await client.request(query, { address: userAddress })
return response.user?.roles.includes(roleHash) || false
}
// Get role assignment history
async function getRoleHistory(userAddress: string) {
const query = `
query UserRoleHistory($account: String!) {
roleActivities(
where: { account: $account },
orderBy: "timestamp",
orderDirection: "desc"
) {
items {
type
role
timestamp
sender
}
}
}
`
return client.request(query, { account: userAddress })
}
// Monitor role statistics
async function getRoleMetrics(roleHash: string) {
const query = `
query RoleMetrics($role: String!) {
roleStat(id: $role) {
totalHolders
lastUpdated
}
roleActivities(
where: { role: $role },
limit: 100,
orderBy: "timestamp",
orderDirection: "desc"
) {
items {
type
timestamp
}
}
}
`
return client.request(query, { role: roleHash })
}React Hook Example
import { useQuery } from '@tanstack/react-query'
import { GraphQLClient } from 'graphql-request'
const client = new GraphQLClient('http://localhost:42069')
export function useUserRoles(address: string) {
return useQuery({
queryKey: ['userRoles', address],
queryFn: async () => {
const query = `
query UserRoles($address: String!) {
user(id: $address) {
id
roles
}
}
`
return client.request(query, { address })
},
enabled: !!address,
staleTime: 1000 * 30, // 30 seconds
})
}
export function useRoleActivities(limit = 10) {
return useQuery({
queryKey: ['roleActivities', limit],
queryFn: async () => {
const query = `
query RecentActivities($limit: Int!) {
roleActivities(
limit: $limit,
orderBy: "timestamp",
orderDirection: "desc"
) {
items {
id
type
role
account
sender
timestamp
}
}
}
`
return client.request(query, { limit })
},
refetchInterval: 5000, // Refresh every 5 seconds
})
}Security Considerations
Data Validation
- Type Safety: Strict TypeScript typing throughout
- Input Sanitization: Address and hash validation
- Range Checks: Timestamp and block number validation
- Consistency Checks: Cross-table data integrity
Access Control
- Read-only API: GraphQL endpoint provides read access only
- Rate Limiting: API request throttling (configurable)
- CORS Configuration: Controlled cross-origin access
- Authentication: Optional API key protection
Error Handling
// Comprehensive error handling in event processors
try {
await processRoleGranted(event)
} catch (error) {
console.error(`Failed to process RoleGranted event: ${event.id}`, error)
// Implement retry logic or alert systems
}Deployment Guide
Local Development
# Start anvil blockchain
pnpm anvil
# Deploy contracts
pnpm contracts:script
# Start indexer
pnpm indexer:dev
# Access GraphQL playground
open http://localhost:42069Production Setup
# Environment configuration
export PONDER_RPC_URL="https://your-rpc-endpoint"
export PONDER_DATABASE_URL="postgresql://..."
# Build and start
pnpm indexer:build
pnpm indexer:startConfiguration
// ponder.config.ts
export default createConfig({
chains: {
mainnet: {
id: 1,
rpc: process.env.PONDER_RPC_URL!,
},
},
contracts: {
Roles: {
chain: "mainnet",
abi: RolesAbi,
address: "0x...", // Deployed contract address
startBlock: 12345678, // Contract deployment block
},
},
})