PackagesAccess

RBAC

The example code snippets used in this guide are experimental and have not been audited. They simply help exemplify usage of the OpenZeppelin Sui Package.

The access_control module provides role-based authorization for Sui Move packages. It is the right choice when authority is not a single transferable object, or when several privileged actors need different permissions over shared protocol state.

Use cases

Use access_control when your protocol needs:

  • A root role controlled by a package One-Time Witness (OTW).
  • Operational roles such as admin, treasurer, operator, guardian, pauser, or keeper.
  • Role-admin relationships, where one role grants and revokes another.
  • Typed authorization proofs with Auth<Role> for new functions.
  • Signature-preserving checks with assert_has_role for existing public functions.
  • Delayed root-role transfer to governance or a multisig.
  • Delayed root-role renounce when the protocol should be permanently locked.
  • Delayed changes to the root-role transfer and renounce delay.

Import

use openzeppelin_access::access_control::{Self, AccessControl, Auth};

Default pattern

The central object is AccessControl<RootRole>. For new code, publish it as a standalone shared object and pass it directly in PTBs. Protected functions receive &Auth<Role> instead of storing the registry inside the protected object.

The module calls the current root-role holder the default_admin. Use new when the publisher should start as that holder, or new_with_admin when a governance or multisig address should hold it from publish.

module my_sui_app::roles;

use openzeppelin_access::access_control::{Self, Auth};

public struct ROLES has drop {}

public struct OperatorRole {}
public struct Treasury has key, store { id: UID }

const DEFAULT_ADMIN_DELAY_MS: u64 = 24 * 60 * 60 * 1_000;

fun init(otw: ROLES, ctx: &mut TxContext) {
    let mut access = access_control::new(otw, DEFAULT_ADMIN_DELAY_MS, ctx);
    access.grant_role<_, OperatorRole>(ctx.sender(), ctx);

    transfer::public_share_object(access);
    transfer::share_object(Treasury { id: object::new(ctx) });
}

public fun protected_action(_: &mut Treasury, _: &Auth<OperatorRole>) {
    // Authorized by Auth<OperatorRole>.
}

The root role cannot be granted, revoked, or renounced with ordinary role-management calls. Use the delayed root transfer or delayed root renounce flows instead. Finalizing a root renounce leaves the registry without a root holder, so use it only when the protocol should become permanently unmanaged.

Root role operations

The root role is the fallback admin for every role. Because it can recover role administration, root-role changes go through delayed flows:

  • begin_default_admin_transfer schedules transfer to a new root holder.
  • accept_default_admin_transfer lets the pending holder accept after the configured delay.
  • begin_default_admin_renounce schedules an intentional lock-in where the current root holder gives up the root role.
  • accept_default_admin_renounce finalizes that lock-in after the configured delay.
  • cancel_default_admin_transfer cancels either pending action and emits a transfer- or renounce-specific cancellation event.
  • default_admin returns the current root-role holder, or none after a finalized renounce.
  • begin_default_admin_delay_change schedules a delay change.
  • cancel_default_admin_delay_change cancels only an active, unelapsed delay change.
  • default_admin_delay_ms(ac, clock) returns the effective delay.

Role grants and root transfers reject @0x0. Root transfers also reject the current root holder as the target. Use the delayed root-renounce flow when the goal is to intentionally lock the registry.

Authorization styles

Use Auth<Role> for new functions when you want authorization to be explicit in the function type and reusable across multiple calls in the same PTB.

Because Auth<Role> is minted from the singleton registry for that role's home module, protected functions do not need to compare registry object IDs when they receive &Auth<Role>. The proof is a PTB-local snapshot: if the signer renounces the role later in the same PTB, an already minted auth value remains valid.

Use assert_has_role when you are retrofitting an existing public function and changing the signature would be disruptive or invalid for package-upgrade compatibility. In that case, the registry is usually stored under an existing Config or State object and borrowed internally.

Learn more

For a full walkthrough of roles, Auth<Role>, publishing, upgrades, and PTB usage, see the Role Based Access Control guide.

For function-level signatures, events, and errors, see the Access API reference.