npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@layerzerolabs/rbac-solana-contracts

v1.2.94

Published

Readme

RBAC

Overview

Role-Based Access Control (RBAC) for Solana Anchor programs, aligned with OpenZeppelin's AccessControl pattern from EVM and adapted for Solana's PDA-based account model. See also docs/role-management/ for cross-VM role management documentation.

The #[rbac] attribute macro generates default Anchor instruction entrypoints and context structs for role management instructions, while allowing developers to override any instruction with custom handlers and context structs.

This crate is built on the generic anchor-trait framework. For the override model (Default / Override), resolution rules, and framework API, see anchor-trait/README.md.

Core Invariant: Role membership is determined solely by the existence of a RoleMember PDA. Granting a role = creating the PDA; revoking = closing it. Overridden instructions must preserve this invariant.

#[rbac] Attribute Macro

Syntax

#[rbac(
    state = StateType,
    role_type = RoleEnumType,
)]
pub mod my_program { ... }

Parameters

| Parameter | Required | Description | | ----------- | -------- | ------------------------------------------------------------------------------------ | | state | Yes | Program state account type. Must implement DefaultAdmin. | | role_type | Yes | Enum type implementing RoleType. Used for role PDA derivation and admin hierarchy. |

To override an instruction, annotate a function inside the module with #[rbac_instruction] — the function name determines which instruction is overridden. See anchor-trait/README.md for the override model.

What the Macro Generates

Prelude (emitted outside the module):

  • Trait imports: RoleType as _, DefaultAdmin as _
  • RoleMember account struct (parameterised by role_type)

Per instruction (subject to override level resolution — see anchor-trait spec):

  1. Default context struct (always emitted for provided instructions)
  2. Canonical entrypoint function (all levels)

RBAC Instructions

| Instruction | Params Type | Return Type | View | Has Default | Authority Check | | ------------------------------- | ------------------------------ | ------------------ | ---- | ----------- | -------------------------- | | grant_role | GrantRoleParams<RoleType> | () | No | Yes | Role admin PDA | | revoke_role | RevokeRoleParams<RoleType> | () | No | Yes | Role admin PDA | | renounce_role | RenounceRoleParams<RoleType> | () | No | Yes | Self (authority == member) | | begin_default_admin_transfer | Pubkey | () | No | Yes | current_default_admin | | accept_default_admin_transfer | None | () | No | Yes | pending_default_admin | | get_default_admin | None | DefaultAdminInfo | Yes | Yes | None |

  • All mutating instructions emit events via emit_cpi!.
  • grant_role, revoke_role, and renounce_role reject DefaultAdmin role — admin transfer must go through the 2-step begin_default_admin_transfer / accept_default_admin_transfer flow.

For default context structs and handler logic, see rbac-macros/src/instructions/ (InstructionSpec).

RoleMember Account

Generated by the #[rbac] macro prelude, parameterised by the program's role_type:

#[account]
#[derive(Default, InitSpace)]
pub struct RoleMember {
    pub state: Pubkey,      // MUST be first field — offset 8 enables RPC filtering
    pub role: RoleType,     // MUST be second field — offset 40
    pub account: Pubkey,
    pub bump: u8,
}

PDA seeds: [ROLE_MEMBER_SEED, state.key(), role.seed(), member.key()]

  • ROLE_MEMBER_SEED = b"RoleMember"
  • state.key() scopes roles per program instance (supports multi-instance programs)
  • role.seed() is a single-byte derived from the RoleType enum discriminant
  • member.key() is the account holding the role

Programs must also embed current_default_admin: Pubkey and pending_default_admin: Pubkey in their root state account, accessed via the DefaultAdmin trait.

Attribute Macros

#[only_role]

Injects a role_member PDA constraint into an accounts struct to enforce that the authority has a specific role. The macro also enforces that authority is a signer.

#[only_role(role = <expr>, state = <ident>, authority = <ident>)]

| Parameter | Required | Description | | ----------- | -------- | ------------------------------------------------------------------- | | role | Yes | Role expression. Static: RoleType::Admin. Dynamic: params.role. | | state | Yes | Field name of the state account used as PDA scope. | | authority | Yes | Field name of the signer to check. |

Generated field (always appended to the struct — do not declare role_member manually, or compilation will fail with a duplicate-field error):

#[account(
    seeds = [ROLE_MEMBER_SEED, state.key(), &RoleType::seed(&role), authority.key()],
    bump = role_member.bump,
    constraint = authority.is_signer @ ::rbac::RbacError::Unauthorized,
    constraint = role_member.role == role @ ::rbac::RbacError::Unauthorized,
)]
pub role_member: Account<'info, RoleMember>,

#[init_default_admin]

Injects the initial admin_role_member PDA and #[event_cpi] into an initialization context struct.

#[init_default_admin(state = <ident>, role_type = <path>, payer = <ident>, admin = <expr>)]

| Parameter | Required | Description | | ----------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | state | Yes | Field name of the state account being initialized. | | role_type | Yes | Path to the RoleType enum. | | payer | Yes | Field that funds account creation. | | admin | Yes | Expression evaluating to a Pubkey that receives the initial DefaultAdmin role (e.g., authority.key() or params.admin). Allows splitting payer and admin. |

The macro injects the admin_role_member account field and #[event_cpi] (for CPI event emission). To complete setup, call the setup_default_admin! proc macro in your initialize handler:

rbac::setup_default_admin!(ctx, state, admin, MyRoleType, payer);

Function-like Macros

setup_default_admin!

Initializes DefaultAdmin state, creates the first RoleMember PDA, and emits a RoleGranted CPI event. Call in your initialize handler after the struct is annotated with #[init_default_admin].

rbac::setup_default_admin!(ctx, <state>, <admin_expr>, <RoleType>, <sender_field>);

| Argument | Type | Description | | ----------- | ----- | ---------------------------------------------------------------- | | ctx | Ident | The handler's context variable. | | state | Ident | Field name of the state account (PDA scope). | | admin | Expr | Expression evaluating to the admin's Pubkey. | | role_type | Path | The RoleType enum path. | | sender | Ident | Signer account field used for the signer check and event sender. |

Derive Macros

#[derive(RoleType)]

Auto-implements RoleType for a #[repr(u8)] enum. The #[default] variant becomes default_admin_role().

#[derive(
    Clone, Copy, PartialEq, Eq, AnchorSerialize, AnchorDeserialize,
    InitSpace, Default, num_enum::TryFromPrimitive, num_enum::IntoPrimitive, RoleType,
)]
#[borsh(use_discriminant = true)]
#[repr(u8)]
pub enum RoleType {
    #[default]
    DefaultAdmin = 0,
    FeeConfigManager = 1,
    Pauser = 2,
    Unpauser = 3,
}

#[derive(DefaultAdmin)]

Auto-implements DefaultAdmin for a struct containing current_default_admin: Pubkey and pending_default_admin: Pubkey fields.

RPC Enumeration

One program can hold many states (one per OFTStore, TokenManager root or policy), and anyone can create one and grant themselves roles in it. RoleMember therefore stores its state at offset 8 (after the 8-byte Anchor discriminator) and its role at offset 40, so a single memcmp on discriminator + state + role enumerates one role of one state via getProgramAccounts:

{
    "method": "getProgramAccounts",
    "params": [
        "<PROGRAM_ID>",
        {
            "filters": [
                { "dataSize": 74 },
                { "memcmp": { "offset": 0, "bytes": "<DISCRIMINATOR_STATE_AND_ROLE_BASE58>" } }
            ]
        }
    ]
}

Drop the role byte from the prefix to list every member of the state. Always include the state: without it, a role filter returns members of every state in the program.

  • EVM: getRoleMember(role, index) — O(1) indexed lookup
  • Solana: getProgramAccounts with memcmp — returns all members at once

EVM Alignment Summary

| OpenZeppelin Concept | Solana RBAC Equivalent | | ------------------------------------- | ----------------------------------------------------------------- | | DEFAULT_ADMIN_ROLE | RoleType::default_admin_role() (#[default] variant) | | hasRole(role, account) | PDA existence check for RoleMember | | grantRole(role, account) | grant_role instruction | | revokeRole(role, account) | revoke_role instruction | | renounceRole(role) | renounce_role instruction | | beginDefaultAdminTransfer(newAdmin) | begin_default_admin_transfer instruction | | acceptDefaultAdminTransfer() | accept_default_admin_transfer instruction | | owner() / defaultAdmin() | get_default_admin view instruction (DefaultAdminInfo.current) | | pendingDefaultAdmin() | get_default_admin view instruction (DefaultAdminInfo.pending) | | getRoleMember(role, index) | getProgramAccounts with memcmp filter | | onlyRole(ROLE) modifier | #[only_role(role = ...)] attribute macro |

Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│  rbac crate                                                                 │
│  pub use rbac_macros::*;  (re-exports all macros)                           │
├─────────────────────────────────────────────────────────────────────────────┤
│  src/                                                                       │
│    Traits: RoleType, DefaultAdmin                                           │
│    Params: GrantRoleParams, RevokeRoleParams, RenounceRoleParams            │
│    Return Types: DefaultAdminInfo                                           │
│    Events: RoleGranted, RoleRevoked, DefaultAdminTransferStarted            │
│    Errors: RbacError                                                        │
│    Constants: ROLE_MEMBER_SEED                                              │
├─────────────────────────────────────────────────────────────────────────────┤
│  macros/ (rbac-macros proc-macro sub-crate)                                 │
│  ┌───────────────────────────────────────────────────────────────────────┐  │
│  │  #[rbac] attribute macro                                              │  │
│  │    - Parses state, role_type                                          │  │
│  │    - Delegates to anchor-trait framework:                             │  │
│  │      collect overrides → generate instructions → assemble output      │  │
│  ├───────────────────────────────────────────────────────────────────────┤  │
│  │  #[only_role]    — injects role_member PDA constraint                 │  │
│  │  #[init_default_admin] — injects admin_role_member + #[event_cpi]     │  │
│  │  setup_default_admin! — inits admin state + emits RoleGranted CPI     │  │
│  │  #[rbac_instruction] — override handler marker (no-op attribute)      │  │
│  ├───────────────────────────────────────────────────────────────────────┤  │
│  │  #[derive(RoleType)]     — validates enum, generates RoleType impl    │  │
│  │  #[derive(DefaultAdmin)] — validates struct, generates impl           │  │
│  ├───────────────────────────────────────────────────────────────────────┤  │
│  │  Per-instruction generators (instructions/)                           │  │
│  │    6 instructions: grant_role, revoke_role, renounce_role,            │  │
│  │    begin_default_admin_transfer, accept_default_admin_transfer,       │  │
│  │    get_default_admin                                                  │  │
│  └──────────────────────────────┬────────────────────────────────────────┘  │
│                                 │ uses                                      │
│                                 ▼                                           │
│  ┌───────────────────────────────────────────────────────────────────────┐  │
│  │  anchor-trait crate (external dependency of macros/)                  │  │
│  │  Override model, InstructionOverrides, generate_instructions,         │  │
│  │  Assembly (see anchor-trait/README.md)                                │  │
│  └───────────────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────────────┘

Generated code emits ::rbac:: paths, resolved at macro expansion in user programs.