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

# 访问控制

> 了解如何在 Private Ephemeral Rollups 中管理细粒度访问控制和成员权限。

***

<CardGroup cols={2}>
  <Card title="Permission Program" icon="github" iconType="duotone" disabled>
    链上权限管理（即将推出）
  </Card>

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

***

## 概述

Private Ephemeral Rollups 是 [Ephemeral Rollups](/cn/pages/ephemeral-rollups-ers/introduction/why) 的一种形式，它在以[合规](/cn/pages/private-ephemeral-rollups-pers/introduction/compliance-framework)为核心的 [Trusted Execution Environment](/cn/pages/private-ephemeral-rollups-pers/introduction/onchain-privacy) 中，为受权限控制的账户提供细粒度权限管理。每个权限账户都会维护一份成员列表，并通过特定标志来决定这些成员可以执行哪些操作。

### 核心概念

* **Permission Account**：用于存储特定账户访问控制规则的 PDA
* **Members**：通过标志授予特定权限的地址
* **Flags**：用于定义成员可执行操作的位掩码（authority、查看日志、查看余额等）
* **Public Permissions**：当成员被设置为 `None` 时，受权限控制的账户会暂时变为可见

***

## 成员标志

成员标志为每位成员定义细粒度权限。你可以通过按位 OR 组合多个标志，以授予多个权限。

**标志说明：**

* **AUTHORITY**：允许成员更新和委托权限设置、添加或移除其他成员，以及更新成员标志。
* **TX\_LOGS**：允许成员查看交易执行日志。
* **TX\_BALANCES**：允许成员查看账户余额变更。
* **TX\_MESSAGE**：允许成员查看交易消息数据。
* **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` 账户完全运行在 Ephemeral Rollup 上，由已委托的 PDA
  承担租金——无需在 Base Layer 上创建、委托或 commit-and-undelegate
  任何权限账户。三个 CPI 操作即可覆盖完整生命周期：**Create**、**Update**、**Close**，
  全部由 ER 上的数据账户作为 PDA 签名，通过 MagicBlock 的 Permission Program
  `ACLseoPoyC3cBqoUtkbjZ4aDrkurZW86v19pXz2XQnp1` 调用。
</Note>

**前置条件 —— 委托数据 PDA。** 只有数据账户会被委托给 TEE 验证器（在 Base
Layer 上，通过 MagicBlock 的 Delegation Program
`DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh`）。委托完成后，PDA 会使用其程序种子
在 ER 上签署全部三个权限操作，并支付 ephemeral permission 的租金——因此必须在
`initialize` 时预先充值。完整流程请参阅
[快速开始](/cn/pages/private-ephemeral-rollups-pers/how-to-guide/quickstart#2-委托并创建-permission)。

<Note>
  <p>
    这些公共验证器可用于开发环境。请确保在你的委托指令中添加对应的
    ER 验证器：
  </p>

  **主网**

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

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

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

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

  **开发网**

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

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

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

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

  **本地网络**

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

<Steps>
  <Step title={<a href="#1-创建-ephemeral-permission">创建 Ephemeral Permission</a>}>
    在 ER 上初始化一个带有初始成员和隐私标志的 `EphemeralPermission` 账户。
    幂等——如果已存在则跳过。
  </Step>

  <Step title={<a href="#2-更新-ephemeral-permission">更新 Ephemeral Permission</a>}>
    切换隐私标志，或添加 / 移除 / 更新成员。更新会立即在 ER 上生效。
  </Step>

  <Step title={<a href="#3-关闭-ephemeral-permission">关闭 Ephemeral Permission</a>}>
    当不再需要权限时，关闭 ER 上的 `EphemeralPermission` 账户，
    并将租金退还给数据 PDA。
  </Step>
</Steps>

***

## Ephemeral Permission 操作

<Tabs>
  <Tab title="1. 创建 Ephemeral Permission">
    通过 `CreateEphemeralPermissionCpi` 在 ER 上初始化一个新的 `EphemeralPermission`
    账户。Payer = 已委托的数据 PDA，它使用其程序种子签名，并使用 `initialize`
    时预先充值的 lamports 来支付租金。

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

    **使用场景：**

    * 为 ER 上新委托的 PDA 启动访问控制
    * 以公开模式启动（`is_private: false`、成员为空），后续通过 Update 收紧

    [⬆️ 返回顶部](#ephemeral-permission)
  </Tab>

  <Tab title="2. 更新 Ephemeral Permission">
    通过 `UpdateEphemeralPermissionCpi` 切换隐私标志并重写成员列表。每次调用都
    重新构建完整成员列表（包含 authority），以确保数据 PDA 永远不会把自己锁在外面。

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

    **使用场景：**

    * 按需切换 `is_private`（如：私密游玩 / 公开揭示）
    * 添加新的查看者，并设置 `TX_LOGS | TX_MESSAGE | TX_BALANCES` 标志
    * 通过在下一次调用的成员列表中省略某个成员来撤销其权限

    [⬆️ 返回顶部](#ephemeral-permission)
  </Tab>

  <Tab title="3. 关闭 Ephemeral Permission">
    通过 `CloseEphemeralPermissionCpi` 关闭 ER 上的 `EphemeralPermission` 账户。
    租金会退还给数据 PDA（原始 payer）。可选——仅在不再需要权限时调用。

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

    **使用场景：**

    * 在取消委托数据 PDA 之前回收 ephemeral 租金
    * 私密执行结束后拆除访问控制

    [⬆️ 返回顶部](#ephemeral-permission)
  </Tab>
</Tabs>

<Tip>
  参考实现：
  [`private-counter`](https://github.com/magicblock-labs/magicblock-engine-examples/tree/main/private-counter)
  （Anchor）和
  [`pinocchio-private-counter`](https://github.com/magicblock-labs/magicblock-engine-examples/tree/main/pinocchio-private-counter)。
</Tip>

***

## 最佳实践

1. **Authority 管理**：始终至少为一位可信成员分配 `AUTHORITY_FLAG`
2. **最小权限原则**：仅向每位成员授予必要的标志
3. **实时更新**：权限可以在不取消委托的情况下于 Private Ephemeral Rollup 上实时更新，从而实现动态访问控制调整
4. **清理**：取消委托并关闭未使用的权限账户，以释放 SOL

***

## 安全注意事项

* **签名者验证**：只有拥有 `AUTHORITY_FLAG` 的成员或带有受权限控制账户的程序才能授权变更
* **公开账户**：将成员设置为 `None` 会使账户对外公开可见
* **默认 Authority**：默认情况下，受权限控制账户的所有者会被添加为权限账户成员中的 permission authority
* **空成员列表**：如果 `members` 字段被设置为空列表，则受权限控制账户将完全受限且保持私密。只有该账户所有者才能修改权限
* **访问审计**：使用成员标志来审计和控制访问

***

<CardGroup cols={2}>
  <Card title="访问控制" icon="lock" href="/cn/pages/private-ephemeral-rollups-pers/how-to-guide/access-control" iconType="duotone">
    细粒度访问控制
  </Card>

  <Card title="链上隐私" icon="shield" href="/cn/pages/private-ephemeral-rollups-pers/introduction/onchain-privacy" iconType="duotone">
    隐私机制与核心概念
  </Card>

  <Card title="授权" icon="key" href="/cn/pages/private-ephemeral-rollups-pers/introduction/authorization" iconType="duotone">
    授权框架
  </Card>

  <Card title="合规框架" icon="certificate" href="/cn/pages/private-ephemeral-rollups-pers/introduction/compliance-framework" iconType="duotone">
    合规标准与指南
  </Card>
</CardGroup>
