users-gearRoles & Permissions

How delegated access to an Onchain Lab works β€” role hierarchy, capability matrix, expiry, agent flag, and the on-chain resolver.

Why Roles Exist

An Onchain Lab's NFT holder is its sole ultimate controller β€” transferring the LabNFT transfers the entire project. In practice, most research projects need to delegate day-to-day data-room work (uploading files, posting announcements, decrypting confidential research) to collaborators and AI agents without surrendering ownership.

The role system lets a Lab owner grant scoped, expiring access to specific wallets β€” human or agent β€” while keeping ownership, treasury control, and the ability to revoke access at any time. Roles are enforced on-chain by the AccessResolver contract and honoured by every downstream system (GraphQL API, file encryption, UI) that checks them.

Role Hierarchy

Every lab has three effective roles, ordered from most to least privileged.

Role
How it's held
Scope

Owner

Holder of the LabNFT, resolved through the Lab's ERC-6551 Token Bound Account (TBA). Safe multisigs holding the NFT are resolved recursively through their signers.

Full control; passes every permission check. Can transfer the LabNFT.

Contributor

Explicit on-chain grant: ROLE_CONTRIBUTOR = 2.

Full data-room access, can grant/revoke Viewers. Cannot add other Contributors or transfer the NFT.

Viewer

Explicit on-chain grant: ROLE_VIEWER = 1.

Read-only. Can decrypt confidential files and read data-room contents.

The hasRole check is hierarchical: a Contributor automatically passes Viewer checks, and the Owner passes every check.

Capability Matrix

Capability
Owner
Contributor
Viewer

View public data-room files

βœ“

βœ“

βœ“

Decrypt confidential data-room files

βœ“

βœ“

βœ“

Upload / update / delete data-room files

βœ“

βœ“

Create announcements

βœ“

βœ“

Grant / revoke Viewer role

βœ“

βœ“

Grant / revoke Contributor role

βœ“

Manage project owners (add/remove)

βœ“

Transfer the LabNFT

βœ“

Authorize / install modules on the Lab

βœ“

A Contributor cannot "downgrade" another Contributor to Viewer β€” downgrades are treated as an admin-level action and rejected for non-Owner callers.

Grants: Expiry & Agent Flag

Each grant is an on-chain record with three fields:

  • Expiry β€” A non-zero expiry makes the grant auto-expire. Expired grants still exist in storage (so getRole returns them for UI purposes) but are inactive: hasRole returns false once block.timestamp >= expiry. Expired grantees must be re-granted to regain access.

  • isAgent β€” Purely informational metadata. It does not change on-chain authorization, but downstream systems (the members list, the data-room UI, the agent-auth flow) surface it to clearly distinguish AI-agent session keys from human team members.

A Lab owner granting access to an agent should set isAgent = true and a short expiry β€” typically the agent's session-key lifetime. When the session expires, the agent must request a new grant before it can continue to decrypt files or post announcements.

Scope: Per-Lab, Not Per-File

Roles are scoped to a lab β€” identified by the canonical oclId (a packed 32-byte identifier combining version, namespace, tokenId, and the TBA address). There is no per-data-room or per-file role; file-level access is enforced by the Onchain-Verified Envelope Encryption layer, which ultimately resolves back to the same AccessResolver predicates (hasRole, isAuthorizedSignerForTba, isAuthorizedSignerForIpnft) when evaluating a decryption request.

For the concrete accessControlConditions JSON that turns a role grant into file-level decryption rights, see Worked Example: Encrypt for Owner OR Contributor OR Viewer.

Chain Scoping

AccessResolver is deployed on Base, Mainnet, and Sepolia, but canonical lab state lives on Base. Lab-owner self-administration works only on Base: the ERC-6551 reference implementation returns address(0) for owner() when block.chainid doesn't match the chain the OCL was CREATE2-salted for, so lab NFT holders on Mainnet or Sepolia must call grantRole / revokeRole through the Base deployment.

Every grantRole / revokeRole / hasRole / getRole call runs _validateOclId, which verifies the oclId's version byte, namespace byte, TBA code, LabNFT binding, and canonical-chain metadata. Malformed identifiers revert with InvalidOclId.

On-Chain Interface

Who may call what

Action
Owner
Active Contributor

grantRole(… Contributor)

βœ“

grantRole(… Viewer) (fresh / same level)

βœ“

βœ“

grantRole(…) that downgrades a role

βœ“

revokeRole(…) on a Contributor

βœ“

revokeRole(…) on a Viewer

βœ“

βœ“

Revokes on accounts with no active grant return silently without emitting an event, to prevent unauthorised callers from spamming RoleRevoked logs.

Events

Use these events to reconstruct the team-members list for a lab off-chain; the on-chain storage is a sparse mapping(oclId => mapping(account => RoleGrant)) and cannot be enumerated without event indexing.

Errors

  • InvalidOclId(bytes32 oclId) β€” malformed identifier or LabNFT binding mismatch.

  • InvalidRole(uint8 role) β€” role must be ROLE_VIEWER (1) or ROLE_CONTRIBUTOR (2).

  • UnauthorizedRoleAdmin(bytes32 oclId, address caller, uint8 role) β€” caller lacks permission for the requested grant/revoke.

See Also

Last updated