> ## 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는 [컴플라이언스](/ko/pages/private-ephemeral-rollups-pers/introduction/compliance-framework)를 핵심에 둔 [Trusted Execution Environment](/ko/pages/private-ephemeral-rollups-pers/introduction/onchain-privacy) 안에서 permissioned accounts에 대한 세밀한 권한 제어를 가능하게 하는 [Ephemeral Rollups](/ko/pages/ephemeral-rollups-ers/introduction/why)입니다. 각 permission account는 멤버 목록과 이들이 수행할 수 있는 작업을 결정하는 특정 플래그를 유지합니다.

### 핵심 개념

* **Permission Account**: 특정 계정에 대한 접근 제어 규칙을 저장하는 PDA
* **Members**: 플래그를 통해 특정 권한이 부여된 주소
* **Flags**: 멤버가 무엇을 할 수 있는지(authority, 로그 보기, 잔액 보기 등)를 정의하는 비트마스크
* **Public Permissions**: 멤버가 `None`으로 설정되면 permissioned account가 일시적으로 보이게 됩니다

***

## 멤버 플래그

멤버 플래그는 각 멤버에 대한 세밀한 권한을 정의합니다. 플래그는 비트 OR로 결합해 여러 권한을 동시에 부여할 수 있습니다.

**플래그 설명:**

* **AUTHORITY**: 멤버가 permission 설정을 업데이트하고 위임하며, 다른 멤버를 추가/제거하고 멤버 플래그를 업데이트할 수 있게 합니다.
* **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에 permission account를 생성하거나 위임하거나
  commit-and-undelegate할 필요가 없습니다. 전체 라이프사이클은 **Create**、**Update**、
  **Close** 세 가지 CPI 작업으로 충분히 다룰 수 있으며, 모두 ER 위의 데이터 계정이
  PDA로 서명하고 MagicBlock의 Permission Program
  `ACLseoPoyC3cBqoUtkbjZ4aDrkurZW86v19pXz2XQnp1`을 통해 실행됩니다.
</Note>

**전제 조건 — 데이터 PDA 위임.** 위임 대상은 데이터 계정뿐입니다(Base Layer에서
MagicBlock의 Delegation Program `DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh`을 통해).
위임 후 PDA는 자신의 프로그램 시드로 ER에서 세 가지 permission 작업 모두에 서명하고
ephemeral permission 렌트를 지불합니다 — 따라서 `initialize` 시점에 미리
충전해 두어야 합니다. 전체 흐름은
[퀵스타트](/ko/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 (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>
      EU (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>}>
    초기 멤버와 프라이버시 플래그를 가진 새 `EphemeralPermission` 계정을 ER에
    초기화합니다. 멱등 — 이미 존재하면 건너뜁니다.
  </Step>

  <Step title={<a href="#2-ephemeral-permission-업데이트">Ephemeral Permission 업데이트</a>}>
    프라이버시 플래그를 전환하거나 멤버를 추가 / 제거 / 플래그 변경합니다.
    업데이트는 ER에서 즉시 반영됩니다.
  </Step>

  <Step title={<a href="#3-ephemeral-permission-닫기">Ephemeral Permission 닫기</a>}>
    permission이 더 이상 필요하지 않을 때 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)에게 환불됩니다. 선택 사항 — permission이
    더 이상 필요하지 않을 때만 호출하세요.

    <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에서 permission을 실시간으로 업데이트해 동적 접근 제어 조정이 가능합니다
4. **정리**: 사용하지 않는 permission accounts를 위임 해제하고 닫아 SOL을 확보하세요

***

## 보안 고려 사항

* **서명자 검증**: `AUTHORITY_FLAG`를 가진 멤버 또는 permissioned account를 가진 프로그램만 변경을 인가할 수 있습니다
* **공개 계정**: 멤버를 `None`으로 설정하면 계정이 공개적으로 보이게 됩니다
* **기본 Authority**: 기본적으로 permissioned account의 소유자가 permission account의 members에 permission authority로 추가됩니다
* **빈 멤버 목록**: `members` 필드가 빈 목록으로 설정되면 permissioned account는 완전히 제한되고 비공개가 됩니다. permission을 수정할 수 있는 것은 소유자뿐입니다
* **접근 감사**: 멤버 플래그를 사용해 접근을 감사하고 제어하세요

***

<CardGroup cols={2}>
  <Card title="접근 제어" icon="lock" href="/ko/pages/private-ephemeral-rollups-pers/how-to-guide/access-control" iconType="duotone">
    세밀한 접근 제어
  </Card>

  <Card title="온체인 프라이버시" icon="shield" href="/ko/pages/private-ephemeral-rollups-pers/introduction/onchain-privacy" iconType="duotone">
    프라이버시 메커니즘과 개념
  </Card>

  <Card title="인가" icon="key" href="/ko/pages/private-ephemeral-rollups-pers/introduction/authorization" iconType="duotone">
    인가 프레임워크
  </Card>

  <Card title="컴플라이언스 프레임워크" icon="certificate" href="/ko/pages/private-ephemeral-rollups-pers/introduction/compliance-framework" iconType="duotone">
    컴플라이언스 기준 및 가이드라인
  </Card>
</CardGroup>
