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.DISTRIBUTORSecurity 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.