> ## Documentation Index
> Fetch the complete documentation index at: https://docs.magicblock.gg/llms.txt
> Use this file to discover all available pages before exploring further.

# Access Control

> Learn how to manage fine-grained access control and member permissions in Private Ephemeral Rollups.

***

<CardGroup cols={2}>
  <Card title="Permission Program" icon="github" iconType="duotone" disabled>
    On-chain Permission Management (Coming soon)
  </Card>

  <Card title="Ephemeral Rollups SDK" icon="github" href="https://github.com/magicblock-labs/ephemeral-rollups-sdk" iconType="duotone">
    SDK for Private Ephemeral Rollups
  </Card>
</CardGroup>

***

## Overview

Private Ephemeral Rollups are [Ephemeral Rollups](/pages/ephemeral-rollups-ers/introduction/why) that enable fine-grained permission over permissioned accounts in a [Trusted Execution Environment](/pages/private-ephemeral-rollups-pers/introduction/onchain-privacy) with [compliance](/pages/private-ephemeral-rollups-pers/introduction/compliance-framework) at its heart. Each permission account maintains a list of members with specific flags that determine what actions they can perform.

### Key Concepts

* **Permission Account**: A PDA that stores access control rules for a specific account
* **Members**: Addresses granted specific permissions via flags
* **Flags**: Bitmasks that define what a member can do (authority, view logs, view balances, etc.)
* **Public Permissions**: When members are set to `None`, the permissioned account becomes temporarily visible

***

## Member Flags

Member flags define fine-grained permissions for each member. Flags can be combined using bitwise OR to grant multiple permissions.

**Flag Descriptions:**

* **AUTHORITY**: Allows a member to update and delegate permission settings, add/remove other members, and update member flags.
* **TX\_LOGS**: Allows a member to view transaction execution logs.
* **TX\_BALANCES**: Allows a member to view account balance changes.
* **TX\_MESSAGE**: Allows a member to view transaction message data.
* **ACCOUNT\_SIGNATURES**: Allows a member to view account signatures

<Tabs>
  <Tab title="Rust SDK">
    ```rust theme={null}
    use ephemeral_rollups_sdk::access_control::structs::{
        Member,
        AUTHORITY_FLAG,
        TX_LOGS_FLAG,
        TX_BALANCES_FLAG,
        TX_MESSAGE_FLAG,
        ACCOUNT_SIGNATURES_FLAG,
    };

    // Set flags by combining them with bitwise OR
    let flags = AUTHORITY_FLAG | TX_LOGS_FLAG;

    // Create a member with combined flags
    let mut member = Member {
        flags,
        pubkey: user_pubkey,
    };

    // Check if member has a specific flag using bitwise AND
    let is_authority = (member.flags & AUTHORITY_FLAG) != 0;
    let can_see_logs = (member.flags & TX_LOGS_FLAG) != 0;

    // Use helper methods to set/remove flags
    member.set_flags(TX_BALANCES_FLAG); // Add a flag
    member.remove_flags(TX_LOGS_FLAG);  // Remove a flag
    ```
  </Tab>

  <Tab title="Pinocchio">
    ```rust theme={null}
    use ephemeral_rollups_pinocchio::types::{Member, MemberFlags};
    use pinocchio::Address;

    // Create and set flags using individual methods
    let mut flags = MemberFlags::new();
    flags.set(MemberFlags::AUTHORITY);
    flags.set(MemberFlags::TX_LOGS);
    flags.set(MemberFlags::TX_BALANCES);

    // Create a member with flags
    let member = Member {
        flags,
        pubkey: user_address,
    };

    // Remove a flag
    flags.remove(MemberFlags::TX_LOGS);

    // Create flags from individual boolean values
    let flags = MemberFlags::from_acl_flags(
        true,  // authority
        true,  // tx_logs
        false, // tx_balances
        true,  // tx_message
        false, // account_signatures
    );

    // Convert flags to byte value
    let flag_byte = flags.to_acl_flag_byte();

    // Create flags from byte value
    let flags = MemberFlags::from_acl_flag_byte(flag_byte);
    ```
  </Tab>

  <Tab title="Web3.js">
    ```typescript theme={null}
    import { PublicKey } from "@solana/web3.js";
    import {
      AUTHORITY_FLAG,
      TX_LOGS_FLAG,
      TX_BALANCES_FLAG,
      TX_MESSAGE_FLAG,
      ACCOUNT_SIGNATURES_FLAG,
      type Member,
    } from "@magicblock-labs/ephemeral-rollups-sdk";

    // Set flags by combining them with bitwise OR
    const flags = AUTHORITY_FLAG | TX_LOGS_FLAG;

    // Create a member with combined flags
    const member: Member = {
      flags,
      pubkey: new PublicKey(userAddress),
    };

    // Check if a flag is present using bitwise AND
    const isAuthority = (member.flags & AUTHORITY_FLAG) !== 0;
    const canSeeLogs = (member.flags & TX_LOGS_FLAG) !== 0;
    const canSeeBalances = (member.flags & TX_BALANCES_FLAG) !== 0;

    // Add a flag to existing flags
    const updatedFlags = member.flags | TX_BALANCES_FLAG;

    // Remove a flag from existing flags
    const removedFlags = member.flags & ~TX_LOGS_FLAG;
    ```
  </Tab>

  <Tab title="Kit">
    ```typescript theme={null}
    import {
      AUTHORITY_FLAG,
      TX_LOGS_FLAG,
      TX_BALANCES_FLAG,
      TX_MESSAGE_FLAG,
      ACCOUNT_SIGNATURES_FLAG,
      isAuthority,
      canSeeTxLogs,
      canSeeTxBalances,
      canSeeTxMessages,
      canSeeAccountSignatures,
      type Member,
    } from "@magicblock-labs/ephemeral-rollups-sdk";

    // Set flags by combining them with bitwise OR
    const flags = AUTHORITY_FLAG | TX_LOGS_FLAG | TX_BALANCES_FLAG;

    // Create a member with combined flags
    const member: Member = {
      flags,
      pubkey: userAddress,
    };

    // Use helper functions to check specific permissions
    const canModifyPermission = isAuthority(member, userAddress);
    const canViewLogs = canSeeTxLogs(member, userAddress);
    const canViewBalances = canSeeTxBalances(member, userAddress);
    const canViewMessages = canSeeTxMessages(member, userAddress);
    const canViewSignatures = canSeeAccountSignatures(member, userAddress);

    // Add a flag to existing member
    const updatedFlags = member.flags | TX_MESSAGE_FLAG;

    // Remove a flag from existing member
    const removedFlags = member.flags & ~TX_LOGS_FLAG;
    ```
  </Tab>
</Tabs>

***

## Ephemeral Permission

<Note>
  `EphemeralPermission` accounts live entirely on the Ephemeral Rollup and are
  paid for by the delegated PDA — no base-layer permission account to create,
  delegate, or commit-and-undelegate. Three CPI ops cover the full lifecycle:
  **Create**, **Update**, **Close** — all PDA-signed by the data account on
  the ER, via MagicBlock's Permission Program `ACLseoPoyC3cBqoUtkbjZ4aDrkurZW86v19pXz2XQnp1`.
</Note>

**Prerequisite — delegate the data PDA.** Only the data account is delegated to
the TEE validator (on the base layer, via MagicBlock's Delegation Program
`DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh`). Once delegated, the PDA signs
all three permission ops on the ER using its program seeds and pays the
ephemeral permission rent — so it must be pre-funded at `initialize` time. See
[Quickstart](/pages/private-ephemeral-rollups-pers/how-to-guide/quickstart#2-delegate-and-create-permission)
for the end-to-end flow.

<Note>
  <p>
    These public validators are supported for development. Make sure to add the
    specific ER validator in your delegation instruction:
  </p>

  **Mainnet**

  <ul>
    <li>
      Asia (as.magicblock.app):{" "}
      <code>MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57</code>
    </li>

    <li>
      EU (eu.magicblock.app):{" "}
      <code>MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e</code>
    </li>

    <li>
      US (us.magicblock.app):{" "}
      <code>MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd</code>
    </li>

    <li>
      TEE (mainnet-tee.magicblock.app):{" "}
      <code>MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo</code>
    </li>
  </ul>

  **Devnet**

  <ul>
    <li>
      Asia (devnet-as.magicblock.app):{" "}
      <code>MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57</code>
    </li>

    <li>
      EU (devnet-eu.magicblock.app):{" "}
      <code>MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e</code>
    </li>

    <li>
      US (devnet-us.magicblock.app):{" "}
      <code>MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd</code>
    </li>

    <li>
      TEE (devnet-tee.magicblock.app):{" "}
      <code>MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo</code>
    </li>
  </ul>

  **Localnet**

  <ul>
    <li>
      Local ER (localhost:7799):{" "}
      <code>mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev</code>
    </li>
  </ul>
</Note>

<Steps>
  <Step title={<a href="#1-create-ephemeral-permission">Create Ephemeral Permission</a>}>
    Initialize a new `EphemeralPermission` account on the ER with initial
    members and the privacy flag. Idempotent — skip if it already exists.
  </Step>

  <Step title={<a href="#2-update-ephemeral-permission">Update Ephemeral Permission</a>}>
    Toggle the privacy flag, or add / remove / reflag members. Updates take
    effect immediately on the ER.
  </Step>

  <Step title={<a href="#3-close-ephemeral-permission">Close Ephemeral Permission</a>}>
    Close the `EphemeralPermission` account on the ER and refund the rent to
    the data PDA when the permission is no longer needed.
  </Step>
</Steps>

***

## Ephemeral Permission Operations

<Tabs>
  <Tab title="1. Create Ephemeral Permission">
    Initialize a new `EphemeralPermission` account on the ER via
    `CreateEphemeralPermissionCpi`. Payer = the delegated data PDA, which
    signs with its program seeds and covers the rent from the lamports
    pre-funded at `initialize` time.

    <Tabs>
      <Tab title="Anchor">
        ```rust theme={null}
        use ephemeral_rollups_sdk::access_control::{
            instructions::CreateEphemeralPermissionCpi,
            structs::{EphemeralMembersArgs, Member},
        };

        // Counter PDA pays for its own permission rent (it carries lamports onto the ER
        // after delegation and signs as PDA via seeds).
        let signers = [
            COUNTER_SEED,
            ctx.accounts.counter.authority.as_ref(),
            &[ctx.bumps.counter],
        ];

        CreateEphemeralPermissionCpi {
            payer: ctx.accounts.counter.to_account_info(),                  // pays ephemeral rent
            permissioned_account: ctx.accounts.counter.to_account_info(),   // what the permission gates
            permission: ctx.accounts.permission.to_account_info(),
            vault: ctx.accounts.ephemeral_vault.to_account_info(),
            magic_program: ctx.accounts.magic_program.to_account_info(),
            permission_program: ctx.accounts.permission_program.to_account_info(),
            args: EphemeralMembersArgs {
                is_private: false,    // start public — flip via UpdateEphemeralPermission
                members: vec![],
            },
        }
        .invoke_signed(&[&signers])?;
        ```
      </Tab>

      <Tab title="Rust SDK">
        ```rust theme={null}
        use ephemeral_rollups_sdk::access_control::{
            instructions::CreateEphemeralPermissionCpi,
            structs::{EphemeralMembersArgs, Member},
        };

        // `permissioned_account` (here a counter PDA) signs as PDA via seeds; pass the
        // same seeds you used for `find_program_address` to derive it.
        let seeds: &[&[u8]] = &[
            COUNTER_SEED,
            permissioned_account_authority.as_ref(),
            &[bump],
        ];

        CreateEphemeralPermissionCpi {
            payer: &counter_account_info,                // pays ephemeral rent
            permissioned_account: &counter_account_info, // what the permission gates
            permission: &permission_account_info,
            vault: &ephemeral_vault_account_info,
            magic_program: &magic_program_account_info,
            permission_program: &permission_program_account_info,
            args: EphemeralMembersArgs {
                is_private: false,   // start public — flip via UpdateEphemeralPermission
                members: vec![],
            },
        }
        .invoke_signed(&[seeds])?;
        ```
      </Tab>

      <Tab title="Pinocchio">
        ```rust theme={null}
        use ephemeral_rollups_pinocchio::acl::{
            CreateEphemeralPermission, EphemeralMembersArgs, Member,
        };
        use pinocchio::cpi::{Seed, Signer};

        // Buffer size: discriminator (8) + EphemeralMembersArgs body.
        // 64 bytes covers up to 1 member with slack for future Update calls.
        const PERMISSION_CPI_BUF: usize = 64;

        // PDA-signed CPI — the counter PDA pays rent and authorizes the permission.
        let bump_seed = [bump];
        let seeds_array: [Seed; 3] = [
            Seed::from(b"counter"),
            Seed::from(authority.address().as_ref()),
            Seed::from(&bump_seed),
        ];
        let signer = Signer::from(&seeds_array);

        let members: [Member; 0] = [];   // start public; toggle via Update
        CreateEphemeralPermission {
            payer: counter_account,
            permissioned_account: counter_account,
            permission,
            vault,
            magic_program,
            permission_program,
            args: EphemeralMembersArgs {
                is_private: false,
                members: &members,
            },
        }
        .invoke_signed::<PERMISSION_CPI_BUF>(&[signer])?;
        ```
      </Tab>

      <Tab title="Kit">
        ```typescript theme={null}
        import {
          MAGIC_PROGRAM_ID,
          PERMISSION_PROGRAM_ID,
          EPHEMERAL_VAULT_ID,
        } from "@magicblock-labs/ephemeral-rollups-sdk";
        import { pipe, createTransactionMessage, appendTransactionMessageInstructions } from "@solana/kit";

        // EphemeralPermissions are created on the ER by the delegated PDA (via the
        // user-program's wrapper instruction). Submit to the ER connection, not base.
        const initIx = await counterProgram.methods
          .initPermission()
          .accountsPartial({
            authority: tempKeypair.address,
            counter: counterPda,
            permission: permissionPda,
            permissionProgram: PERMISSION_PROGRAM_ID,
            ephemeralVault: EPHEMERAL_VAULT_ID,
            magicProgram: MAGIC_PROGRAM_ID,
          })
          .instruction();

        const transactionMessage = pipe(
          createTransactionMessage({ version: 0 }),
          (tx) => appendTransactionMessageInstructions([initIx], tx),
        );
        const sig = await ephemeralConnection.sendAndConfirmTransaction(
          transactionMessage,
          [tempKeypair],
          { commitment: "confirmed" },
        );
        console.log("init_permission tx:", sig);
        ```
      </Tab>

      <Tab title="Web3.js">
        ```typescript theme={null}
        import {
          MAGIC_PROGRAM_ID,
          PERMISSION_PROGRAM_ID,
          EPHEMERAL_VAULT_ID,
        } from "@magicblock-labs/ephemeral-rollups-sdk";
        import { Transaction, sendAndConfirmTransaction } from "@solana/web3.js";

        // EphemeralPermissions are created on the ER by the delegated PDA (via the
        // user-program's wrapper instruction). Submit to the ER connection, not base.
        const initIx = await counterProgram.methods
          .initPermission()
          .accountsPartial({
            authority: tempKeypair.publicKey,
            counter: counterPda,
            permission: permissionPda,
            permissionProgram: PERMISSION_PROGRAM_ID,
            ephemeralVault: EPHEMERAL_VAULT_ID,
            magicProgram: MAGIC_PROGRAM_ID,
          })
          .instruction();

        const tx = new Transaction().add(initIx);
        const sig = await sendAndConfirmTransaction(ephemeralConnection, tx, [tempKeypair]);
        console.log("init_permission tx:", sig);
        ```
      </Tab>
    </Tabs>

    **Use Cases:**

    * Bootstrap access control for a newly delegated PDA on the ER
    * Start public (`is_private: false`, empty members) and tighten later via Update

    [⬆️ Back to Top](#ephemeral-permission)
  </Tab>

  <Tab title="2. Update Ephemeral Permission">
    Flip the privacy flag and rewrite the member list via
    `UpdateEphemeralPermissionCpi`. Rebuild the full member list every call
    (including the authority) so the data PDA can never lock itself out.

    <Tabs>
      <Tab title="Anchor">
        ```rust theme={null}
        use ephemeral_rollups_sdk::access_control::{
            instructions::UpdateEphemeralPermissionCpi,
            structs::{
                EphemeralMembersArgs, Member,
                TX_LOGS_FLAG, TX_MESSAGE_FLAG, TX_BALANCES_FLAG,
            },
        };

        let signers = [
            COUNTER_SEED,
            ctx.accounts.counter.authority.as_ref(),
            &[ctx.bumps.counter],
        ];

        // When private, only listed members can read ER state via the TEE.
        // Empty member list + is_private=false = fully public.
        let members = if is_private {
            vec![Member {
                flags: TX_LOGS_FLAG | TX_MESSAGE_FLAG | TX_BALANCES_FLAG,
                pubkey: ctx.accounts.counter.authority,
            }]
        } else {
            vec![]
        };

        UpdateEphemeralPermissionCpi {
            payer: ctx.accounts.counter.to_account_info(),
            permissioned_account: ctx.accounts.counter.to_account_info(),
            permission: ctx.accounts.permission.to_account_info(),
            vault: ctx.accounts.ephemeral_vault.to_account_info(),
            magic_program: ctx.accounts.magic_program.to_account_info(),
            permission_program: ctx.accounts.permission_program.to_account_info(),
            authority: ctx.accounts.counter.to_account_info(),
            authority_is_signer: false,    // PDA signs via the seeds above
            args: EphemeralMembersArgs { is_private, members },
        }
        .invoke_signed(&[&signers])?;
        ```
      </Tab>

      <Tab title="Rust SDK">
        ```rust theme={null}
        use ephemeral_rollups_sdk::access_control::{
            instructions::UpdateEphemeralPermissionCpi,
            structs::{
                EphemeralMembersArgs, Member,
                TX_LOGS_FLAG, TX_MESSAGE_FLAG, TX_BALANCES_FLAG,
            },
        };

        let seeds: &[&[u8]] = &[
            COUNTER_SEED,
            permissioned_account_authority.as_ref(),
            &[bump],
        ];

        // When private, only listed members can read ER state via the TEE.
        let members = if is_private {
            vec![Member {
                flags: TX_LOGS_FLAG | TX_MESSAGE_FLAG | TX_BALANCES_FLAG,
                pubkey: permissioned_account_authority,
            }]
        } else {
            vec![]
        };

        UpdateEphemeralPermissionCpi {
            payer: &counter_account_info,
            permissioned_account: &counter_account_info,
            permission: &permission_account_info,
            vault: &ephemeral_vault_account_info,
            magic_program: &magic_program_account_info,
            permission_program: &permission_program_account_info,
            authority: &counter_account_info,
            authority_is_signer: false,   // PDA signs via the seeds above
            args: EphemeralMembersArgs { is_private, members },
        }
        .invoke_signed(&[seeds])?;
        ```
      </Tab>

      <Tab title="Pinocchio">
        ```rust theme={null}
        use ephemeral_rollups_pinocchio::acl::{
            EphemeralMembersArgs, Member, MemberFlags, UpdateEphemeralPermission,
        };
        use pinocchio::cpi::{Seed, Signer};

        const PERMISSION_CPI_BUF: usize = 64;

        let bump_seed = [bump];
        let seeds_array: [Seed; 3] = [
            Seed::from(b"counter"),
            Seed::from(authority.address().as_ref()),
            Seed::from(&bump_seed),
        ];
        let signer = Signer::from(&seeds_array);

        // Read the on-chain Counter to grab `authority` — the sole "private" member.
        let counter_authority = {
            let data = counter_account.try_borrow()?;
            Counter::load(&data)?.authority
        };

        let single_member = [Member {
            flags: MemberFlags::from_acl_flag_byte(
                MemberFlags::TX_LOGS | MemberFlags::TX_MESSAGE | MemberFlags::TX_BALANCES,
            ),
            pubkey: counter_authority,
        }];
        let members: &[Member] = if is_private { &single_member } else { &[] };

        UpdateEphemeralPermission {
            payer: counter_account,
            permissioned_account: counter_account,
            permission,
            vault,
            magic_program,
            permission_program,
            authority: counter_account,
            authority_is_signer: false,   // PDA signs via the seeds above
            args: EphemeralMembersArgs { is_private, members },
        }
        .invoke_signed::<PERMISSION_CPI_BUF>(&[signer])?;
        ```
      </Tab>

      <Tab title="Kit">
        ```typescript theme={null}
        import {
          MAGIC_PROGRAM_ID,
          PERMISSION_PROGRAM_ID,
          EPHEMERAL_VAULT_ID,
        } from "@magicblock-labs/ephemeral-rollups-sdk";
        import { pipe, createTransactionMessage, appendTransactionMessageInstructions } from "@solana/kit";

        // Toggle the `is_private` flag. Idempotent — the program rebuilds the member
        // list every call so the authority never locks itself out.
        const updateIx = await counterProgram.methods
          .setPrivacy(isPrivate)
          .accountsPartial({
            authority: tempKeypair.address,
            counter: counterPda,
            permission: permissionPda,
            permissionProgram: PERMISSION_PROGRAM_ID,
            ephemeralVault: EPHEMERAL_VAULT_ID,
            magicProgram: MAGIC_PROGRAM_ID,
          })
          .instruction();

        const transactionMessage = pipe(
          createTransactionMessage({ version: 0 }),
          (tx) => appendTransactionMessageInstructions([updateIx], tx),
        );
        const sig = await ephemeralConnection.sendAndConfirmTransaction(
          transactionMessage,
          [tempKeypair],
          { commitment: "confirmed" },
        );
        console.log("set_privacy tx:", sig);
        ```
      </Tab>

      <Tab title="Web3.js">
        ```typescript theme={null}
        import {
          MAGIC_PROGRAM_ID,
          PERMISSION_PROGRAM_ID,
          EPHEMERAL_VAULT_ID,
        } from "@magicblock-labs/ephemeral-rollups-sdk";
        import { Transaction, sendAndConfirmTransaction } from "@solana/web3.js";

        // Toggle the `is_private` flag. Idempotent — the program rebuilds the member
        // list every call so the authority never locks itself out.
        const updateIx = await counterProgram.methods
          .setPrivacy(isPrivate)
          .accountsPartial({
            authority: tempKeypair.publicKey,
            counter: counterPda,
            permission: permissionPda,
            permissionProgram: PERMISSION_PROGRAM_ID,
            ephemeralVault: EPHEMERAL_VAULT_ID,
            magicProgram: MAGIC_PROGRAM_ID,
          })
          .instruction();

        const tx = new Transaction().add(updateIx);
        const sig = await sendAndConfirmTransaction(ephemeralConnection, tx, [tempKeypair]);
        console.log("set_privacy tx:", sig);
        ```
      </Tab>
    </Tabs>

    **Use Cases:**

    * Toggle `is_private` on demand (e.g. private play, public reveal)
    * Add new viewers with `TX_LOGS | TX_MESSAGE | TX_BALANCES` flags
    * Revoke a member by omitting them from the next call's member list

    [⬆️ Back to Top](#ephemeral-permission)
  </Tab>

  <Tab title="3. Close Ephemeral Permission">
    Close the `EphemeralPermission` account on the ER via
    `CloseEphemeralPermissionCpi`. Rent is refunded to the data PDA (the
    original payer). Optional — only call when the permission is no longer
    needed.

    <Tabs>
      <Tab title="Anchor">
        ```rust theme={null}
        use ephemeral_rollups_sdk::access_control::instructions::CloseEphemeralPermissionCpi;

        let signers = [
            COUNTER_SEED,
            ctx.accounts.counter.authority.as_ref(),
            &[ctx.bumps.counter],
        ];

        // Refunds the permission's rent to `payer` (the counter PDA).
        CloseEphemeralPermissionCpi {
            payer: ctx.accounts.counter.to_account_info(),
            permissioned_account: ctx.accounts.counter.to_account_info(),
            permission: ctx.accounts.permission.to_account_info(),
            vault: ctx.accounts.ephemeral_vault.to_account_info(),
            magic_program: ctx.accounts.magic_program.to_account_info(),
            permission_program: ctx.accounts.permission_program.to_account_info(),
            authority: ctx.accounts.counter.to_account_info(),
            authority_is_signer: false,
        }
        .invoke_signed(&[&signers])?;
        ```
      </Tab>

      <Tab title="Rust SDK">
        ```rust theme={null}
        use ephemeral_rollups_sdk::access_control::instructions::CloseEphemeralPermissionCpi;

        let seeds: &[&[u8]] = &[
            COUNTER_SEED,
            permissioned_account_authority.as_ref(),
            &[bump],
        ];

        // Refunds the permission's rent to `payer` (the counter PDA).
        CloseEphemeralPermissionCpi {
            payer: &counter_account_info,
            permissioned_account: &counter_account_info,
            permission: &permission_account_info,
            vault: &ephemeral_vault_account_info,
            magic_program: &magic_program_account_info,
            permission_program: &permission_program_account_info,
            authority: &counter_account_info,
            authority_is_signer: false,
        }
        .invoke_signed(&[seeds])?;
        ```
      </Tab>

      <Tab title="Pinocchio">
        ```rust theme={null}
        use ephemeral_rollups_pinocchio::acl::CloseEphemeralPermission;
        use pinocchio::cpi::{Seed, Signer};

        let bump_seed = [bump];
        let seeds_array: [Seed; 3] = [
            Seed::from(b"counter"),
            Seed::from(authority.address().as_ref()),
            Seed::from(&bump_seed),
        ];
        let signer = Signer::from(&seeds_array);

        // Refunds the permission's rent to `payer` (the counter PDA).
        CloseEphemeralPermission {
            payer: counter_account,
            permissioned_account: counter_account,
            permission,
            vault,
            magic_program,
            permission_program,
            authority: counter_account,
            authority_is_signer: false,
        }
        .invoke_signed(&[signer])?;
        ```
      </Tab>

      <Tab title="Kit">
        ```typescript theme={null}
        import {
          MAGIC_PROGRAM_ID,
          PERMISSION_PROGRAM_ID,
          EPHEMERAL_VAULT_ID,
        } from "@magicblock-labs/ephemeral-rollups-sdk";
        import { pipe, createTransactionMessage, appendTransactionMessageInstructions } from "@solana/kit";

        // Refunds the permission's rent to the counter PDA.
        const closeIx = await counterProgram.methods
          .closePermission()
          .accountsPartial({
            authority: tempKeypair.address,
            counter: counterPda,
            permission: permissionPda,
            permissionProgram: PERMISSION_PROGRAM_ID,
            ephemeralVault: EPHEMERAL_VAULT_ID,
            magicProgram: MAGIC_PROGRAM_ID,
          })
          .instruction();

        const transactionMessage = pipe(
          createTransactionMessage({ version: 0 }),
          (tx) => appendTransactionMessageInstructions([closeIx], tx),
        );
        const sig = await ephemeralConnection.sendAndConfirmTransaction(
          transactionMessage,
          [tempKeypair],
          { commitment: "confirmed" },
        );
        console.log("close_permission tx:", sig);
        ```
      </Tab>

      <Tab title="Web3.js">
        ```typescript theme={null}
        import {
          MAGIC_PROGRAM_ID,
          PERMISSION_PROGRAM_ID,
          EPHEMERAL_VAULT_ID,
        } from "@magicblock-labs/ephemeral-rollups-sdk";
        import { Transaction, sendAndConfirmTransaction } from "@solana/web3.js";

        // Refunds the permission's rent to the counter PDA.
        const closeIx = await counterProgram.methods
          .closePermission()
          .accountsPartial({
            authority: tempKeypair.publicKey,
            counter: counterPda,
            permission: permissionPda,
            permissionProgram: PERMISSION_PROGRAM_ID,
            ephemeralVault: EPHEMERAL_VAULT_ID,
            magicProgram: MAGIC_PROGRAM_ID,
          })
          .instruction();

        const tx = new Transaction().add(closeIx);
        const sig = await sendAndConfirmTransaction(ephemeralConnection, tx, [tempKeypair]);
        console.log("close_permission tx:", sig);
        ```
      </Tab>
    </Tabs>

    **Use Cases:**

    * Reclaim ephemeral rent before undelegating the data PDA
    * Tear down access control once private execution is finished

    [⬆️ Back to Top](#ephemeral-permission)
  </Tab>
</Tabs>

<Tip>
  Reference implementations:
  [`private-counter`](https://github.com/magicblock-labs/magicblock-engine-examples/tree/main/private-counter)
  (Anchor)
  and
  [`pinocchio-private-counter`](https://github.com/magicblock-labs/magicblock-engine-examples/tree/main/pinocchio-private-counter).
</Tip>

***

## Best Practices

1. **Authority Management**: Always assign AUTHORITY\_FLAG to at least one trusted member
2. **Least Privilege**: Grant only necessary flags to each member
3. **Real-time Updates**: Permissions can be updated in real-time on Private Ephemeral Rollup without undelegating, allowing dynamic access control adjustments
4. **Cleanup**: Undelegate and close unused permission accounts to free SOL

***

## Security Considerations

* **Signer Validation**: Only members with AUTHORITY\_FLAG or program with permissioned account can authorize changes
* **Public Accounts**: Setting members to `None` makes the account publicly visible
* **Default Authority**: By default, the owner of the permissioned account is added as permission authority to members of permission account.
* **Empty Member List**: If members field is set to empty list, the permissioned account is fully restricted and private. Only the owner of permissioned account can modify the permission.
* **Access Auditing**: Use member flags to audit and control access

***

<CardGroup cols={2}>
  <Card title="Access Control" icon="lock" href="/pages/private-ephemeral-rollups-pers/how-to-guide/access-control" iconType="duotone">
    Fine-grained Access Control
  </Card>

  <Card title="On-chain Privacy" icon="shield" href="/pages/private-ephemeral-rollups-pers/introduction/onchain-privacy" iconType="duotone">
    Privacy Mechanisms and Concepts
  </Card>

  <Card title="Authorization" icon="key" href="/pages/private-ephemeral-rollups-pers/introduction/authorization" iconType="duotone">
    Authorization Framework
  </Card>

  <Card title="Compliance Framework" icon="certificate" href="/pages/private-ephemeral-rollups-pers/introduction/compliance-framework" iconType="duotone">
    Compliance Standards and Guidelines
  </Card>
</CardGroup>
