Skip to content

Role Management System

A comprehensive role-based access control system built with IFIF protocol, demonstrating hierarchical permissions, user management, and administrative controls.

Overview

The Role Management System showcases how to implement enterprise-grade access control using the IFIF Roles smart contract. It provides a complete Web3 application with real-time indexing, transaction management, and intuitive user interfaces.

Key Features

  • Hierarchical Role System: Admin → Manager → Distributor role hierarchy
  • User Management: Role assignment, revocation, and transfer capabilities
  • Activity Tracking: Complete audit trail of all role operations
  • Real-time Analytics: Role distribution statistics and activity monitoring
  • Administrative Controls: Owner-level management and emergency controls

Smart Contract Integration

Role Contract Hooks

The system provides type-safe React hooks for all role operations:

Role Assignment

import { useGrantRole } from '@/lib/contract-hooks'
 
function AssignRoleButton() {
  const { grantRole, isLoading, isSuccess, error, txHash, reset } = useGrantRole()
  
  const handleAssignRole = async (roleId: string, account: string) => {
    try {
      const hash = await grantRole({ roleId, account })
      console.log('Transaction hash:', hash)
    } catch (err) {
      console.error('Failed to grant role:', err)
    }
  }
 
  return (
    <Button 
      onClick={() => handleAssignRole('0xa49807205ce4d355092ef5a8a18f56e8913cf4a201fbe287825b095693c21775', '0x123...')}
      disabled={isLoading}
    >
      {isLoading ? 'Granting...' : 'Grant Role'}
    </Button>
  )
}

Role Revocation

import { useRevokeRole } from '@/lib/contract-hooks'
 
function RevokeRoleButton() {
  const { revokeRole, isLoading, isSuccess, error, txHash } = useRevokeRole()
  
  const handleRevokeRole = async (roleId: string, account: string) => {
    try {
      await revokeRole({ roleId, account })
    } catch (err) {
      console.error('Failed to revoke role:', err)
    }
  }
 
  return (
    <Button 
      onClick={() => handleRevokeRole('0xa49807205ce4d355092ef5a8a18f56e8913cf4a201fbe287825b095693c21775', '0x123...')}
      disabled={isLoading}
      variant="destructive"
    >
      {isLoading ? 'Revoking...' : 'Revoke Role'}
    </Button>
  )
}

Role Queries

import { useHasRole, useRoleAdmin } from '@/lib/contract-hooks'
 
function UserRoleDisplay({ address, roleId }: { address: string, roleId: string }) {
  const { hasRole, isLoading: roleLoading, checkRole } = useHasRole(roleId, address)
  const { admin, isLoading: adminLoading, getRoleAdmin } = useRoleAdmin(roleId)
  
  if (roleLoading || adminLoading) return <div>Loading role information...</div>
  
  return (
    <div>
      <h3>Role Information:</h3>
      <div className="space-y-2">
        <p>Has Role: {hasRole ? 'Yes' : 'No'}</p>
        <p>Role Admin: <code className="text-sm">{admin}</code></p>
      </div>
      
      <Button onClick={checkRole} variant="outline">
        Refresh Role Status
      </Button>
    </div>
  )
}

High-Level Role Management

import { useRole } from '@/lib/use-role'
 
function UserRoleManager({ userId }: { userId: string }) {
  const {
    roleInfo,
    grantRole,
    revokeRole,
    refreshRole,
    isLoading,
    isOperating
  } = useRole({
    roleId: '0xa49807205ce4d355092ef5a8a18f56e8913cf4a201fbe287825b095693c21775',
    account: userId as Address,
    autoFetch: true,
    onGrantSuccess: (txHash) => {
      console.log('Role granted!', txHash)
    },
    onRevokeSuccess: (txHash) => {
      console.log('Role revoked!', txHash)
    },
    onError: (error) => {
      console.error('Role operation failed:', error)
    }
  })
 
  return (
    <div className="p-4 border rounded">
      <h3>Role Management</h3>
      
      {isLoading && <p>Loading role information...</p>}
      
      <div className="space-y-2">
        <p>Has Role: {roleInfo.hasRole ? 'Yes' : 'No'}</p>
        <p>Admin: <code className="text-sm">{roleInfo.admin}</code></p>
        <p>Member Count: {roleInfo.memberCount}</p>
      </div>
 
      <div className="space-y-2 mt-4">
        <Button 
          onClick={() => grantRole()}
          disabled={isOperating || roleInfo.hasRole}
        >
          Grant Role
        </Button>
        
        <Button 
          onClick={() => revokeRole()}
          disabled={isOperating || !roleInfo.hasRole}
          variant="destructive"
        >
          Revoke Role
        </Button>
        
        <Button onClick={refreshRole} variant="outline">
          Refresh
        </Button>
      </div>
    </div>
  )
}

Transaction Management

All role operations include comprehensive transaction tracking:

import { useTransactionQueue } from '@/lib/transaction-queue-store'
 
function TransactionStatus() {
  const { transactions, clearCompleted } = useTransactionQueue()
  
  return (
    <div>
      <h3>Recent Transactions</h3>
      {transactions.map(tx => (
        <div key={tx.id} className="flex items-center gap-2">
          <Badge variant={tx.status === 'success' ? 'default' : 'destructive'}>
            {tx.status}
          </Badge>
          <span>{tx.title}</span>
          {tx.hash && (
            <ExternalLink href={`https://etherscan.io/tx/${tx.hash}`}>
              View on Etherscan
            </ExternalLink>
          )}
        </div>
      ))}
      
      <Button onClick={clearCompleted} variant="outline" size="sm">
        Clear Completed
      </Button>
    </div>
  )
}

Data Layer Integration

Real-time Role Data

The system uses Ponder for real-time blockchain indexing with progressive loading:

import { useCachedRoleData } from '@/lib/progressive-data-hooks'
 
function RoleAnalytics() {
  const { 
    roleActivities, 
    roleStats, 
    users,
    isActivitiesLoading,
    isStatsLoading,
    isUsersLoading
  } = useCachedRoleData()
  
  if (isActivitiesLoading || isStatsLoading || isUsersLoading) {
    return <div>Loading analytics data...</div>
  }
  
  return (
    <div className="grid grid-cols-3 gap-4">
      <Card>
        <CardContent>
          <div className="text-2xl font-bold">{roleStats?.filter(s => s.id === ADMIN_ROLE)[0]?.totalHolders || 0}</div>
          <p className="text-sm text-muted-foreground">Total Admins</p>
        </CardContent>
      </Card>
      
      <Card>
        <CardContent>
          <div className="text-2xl font-bold">{roleStats?.filter(s => s.id === MANAGER_ROLE)[0]?.totalHolders || 0}</div>
          <p className="text-sm text-muted-foreground">Total Managers</p>
        </CardContent>
      </Card>
      
      <Card>
        <CardContent>
          <div className="text-2xl font-bold">{roleStats?.filter(s => s.id === DISTRIBUTOR_ROLE)[0]?.totalHolders || 0}</div>
          <p className="text-sm text-muted-foreground">Total Distributors</p>
        </CardContent>
      </Card>
    </div>
  )
}

Activity Monitoring

Real-time activity tracking with progressive loading:

import { useCachedRoleData } from '@/lib/progressive-data-hooks'
 
function RoleActivityTable() {
  const { roleActivities, isActivitiesLoading } = useCachedRoleData()
  
  const columns = [
    {
      accessorKey: 'timestamp',
      header: 'Time',
      cell: ({ row }) => formatDate(new Date(Number(row.getValue('timestamp')) * 1000))
    },
    {
      accessorKey: 'type',
      header: 'Action',
      cell: ({ row }) => {
        const type = row.getValue('type')
        return (
          <Badge variant={type === 0 ? 'default' : 'destructive'}>
            {type === 0 ? 'GRANTED' : 'REVOKED'}
          </Badge>
        )
      }
    },
    {
      accessorKey: 'role',
      header: 'Role',
      cell: ({ row }) => (
        <code className="text-sm">{shortenHash(row.getValue('role'))}</code>
      )
    },
    {
      accessorKey: 'account',
      header: 'User',
      cell: ({ row }) => (
        <code className="text-sm">{shortenAddress(row.getValue('account'))}</code>
      )
    },
    {
      accessorKey: 'sender',
      header: 'Granted By',
      cell: ({ row }) => (
        <code className="text-sm">{shortenAddress(row.getValue('sender'))}</code>
      )
    }
  ]
  
  return (
    <DataTable
      data={roleActivities || []}
      columns={columns}
      loading={isActivitiesLoading}
    />
  )
}

User Interface Patterns

Multi-Role Management

Complete role management for multiple roles:

import { useAccountRoles } from '@/lib/use-role'
 
function UserRoleManagement({ account }: { account: string }) {
  const {
    roles,
    grantRole,
    revokeRole,
    refreshAllRoles,
    isLoading,
    isOperating
  } = useAccountRoles({
    account,
    roleIds: [
      '0xa49807205ce4d355092ef5a8a18f56e8913cf4a201fbe287825b095693c21775', // ADMIN
      '0x241ecf16d79d0f8dbfb92cbc07fe17840425976cf0667f022fe9877caa831b08', // MANAGER  
      '0xfbd454f36a7e1a388bd6fc3ab10d434aa4578f811acbbcf33afb1c697486313c'  // DISTRIBUTOR
    ],
    autoFetch: true
  })
 
  if (isLoading) return <div>Loading roles...</div>
 
  return (
    <div className="space-y-4">
      <h2>User Roles</h2>
      
      {roles.map((role) => (
        <div key={role.roleId} className="flex items-center justify-between p-3 border rounded">
          <div>
            <h3>{role.roleData?.name || 'Unknown Role'}</h3>
            <p>Status: {role.hasRole ? 'Granted' : 'Not Granted'}</p>
            <p>Members: {role.memberCount}</p>
          </div>
          
          <div className="space-x-2">
            {role.hasRole ? (
              <Button 
                onClick={() => revokeRole(role.roleId)}
                disabled={isOperating}
                variant="destructive"
                size="sm"
              >
                Revoke
              </Button>
            ) : (
              <Button 
                onClick={() => grantRole(role.roleId)}
                disabled={isOperating}
                size="sm"
              >
                Grant
              </Button>
            )}
          </div>
        </div>
      ))}
      
      <Button onClick={refreshAllRoles} variant="outline">
        Refresh All
      </Button>
    </div>
  )
}

Bulk Role Operations

import { useBulkRoleOperations } from '@/lib/use-role'
 
function BulkRoleManager() {
  const { grantMultipleRoles, revokeMultipleRoles, isProcessing } = useBulkRoleOperations()
 
  const handleBulkGrant = async () => {
    const results = await grantMultipleRoles({
      account: '0x742d35Cc6635C0532925a3b8D8C3e4Ad80e57A3c',
      roleIds: [
        '0xa49807205ce4d355092ef5a8a18f56e8913cf4a201fbe287825b095693c21775',
        '0x241ecf16d79d0f8dbfb92cbc07fe17840425976cf0667f022fe9877caa831b08'
      ]
    })
    
    console.log('Bulk grant results:', results)
  }
 
  const handleBulkRevoke = async () => {
    const results = await revokeMultipleRoles({
      account: '0x742d35Cc6635C0532925a3b8D8C3e4Ad80e57A3c',
      roleIds: [
        '0xa49807205ce4d355092ef5a8a18f56e8913cf4a201fbe287825b095693c21775',
        '0x241ecf16d79d0f8dbfb92cbc07fe17840425976cf0667f022fe9877caa831b08'
      ]
    })
    
    console.log('Bulk revoke results:', results)
  }
 
  return (
    <div className="space-y-4">
      <Button 
        onClick={handleBulkGrant}
        disabled={isProcessing}
      >
        {isProcessing ? 'Processing...' : 'Grant Multiple Roles'}
      </Button>
      
      <Button 
        onClick={handleBulkRevoke}
        disabled={isProcessing}
        variant="destructive"
      >
        {isProcessing ? 'Processing...' : 'Revoke Multiple Roles'}
      </Button>
    </div>
  )
}

Error Handling

Comprehensive error handling patterns:

import { toast } from 'sonner'
 
function RoleOperationHandler() {
  const { grantRole } = useGrantRole()
  
  const handleRoleOperation = (roleId: string, account: string) => {
    grantRole(
      { roleId, account },
      {
        onSuccess: (data) => {
          toast.success('Role assigned successfully', {
            description: `Role assigned to ${account}`,
            action: {
              label: 'View Transaction',
              onClick: () => window.open(`https://etherscan.io/tx/${data}`)
            }
          })
        },
        onError: (error) => {
          if (error.includes('insufficient permissions')) {
            toast.error('Access Denied', {
              description: 'You do not have permission to assign this role'
            })
          } else if (error.includes('user rejected')) {
            toast.error('Transaction Cancelled', {
              description: 'You cancelled the transaction'
            })
          } else {
            toast.error('Transaction Failed', {
              description: error
            })
          }
        }
      }
    )
  }
}

Role Constants

The system uses the following standard role identifiers:

// Standard role hashes from the Roles contract
export const ROLES = {
  ADMIN: '0xa49807205ce4d355092ef5a8a18f56e8913cf4a201fbe287825b095693c21775',
  MANAGER: '0x241ecf16d79d0f8dbfb92cbc07fe17840425976cf0667f022fe9877caa831b08', 
  DISTRIBUTOR: '0xfbd454f36a7e1a388bd6fc3ab10d434aa4578f811acbbcf33afb1c697486313c'
} as const
 
// Usage in components
const ADMIN_ROLE = ROLES.ADMIN
const MANAGER_ROLE = ROLES.MANAGER
const DISTRIBUTOR_ROLE = ROLES.DISTRIBUTOR

Security Considerations

  • Role Hierarchy: The contract enforces proper role hierarchy automatically
  • Ownership Protection: Owner cannot renounce admin role - must transfer ownership
  • Access Control: All UI operations validate permissions before execution
  • Input Validation: All addresses and role IDs are validated before contract calls
  • Transaction Security: All transaction parameters are verified before signing

Performance Optimization

  • Progressive Loading: Uses progressive data loading for large datasets
  • Data Caching: React Query caches data with background updates
  • Component Optimization: Proper memoization for expensive operations
  • Bundle Optimization: Code split by role functionality
  • Database Indexing: Optimized Ponder schema for common queries

The Role Management System demonstrates a complete enterprise-grade implementation of role-based access control with modern Web3 development practices.