Private Ephemeral Rollups are Ephemeral Rollups that enable fine-grained permission over permissioned accounts in a Trusted Execution Environment with compliance at its heart. Each permission account maintains a list of members with specific flags that determine what actions they can perform.
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
Rust SDK
Pinocchio
Web3.js
Kit
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 ORlet flags = AUTHORITY_FLAG | TX_LOGS_FLAG;// Create a member with combined flagslet mut member = Member { flags, pubkey: user_pubkey,};// Check if member has a specific flag using bitwise ANDlet is_authority = (member.flags & AUTHORITY_FLAG) != 0;let can_see_logs = (member.flags & TX_LOGS_FLAG) != 0;// Use helper methods to set/remove flagsmember.set_flags(TX_BALANCES_FLAG); // Add a flagmember.remove_flags(TX_LOGS_FLAG); // Remove a flag
use ephemeral_rollups_pinocchio::types::{Member, MemberFlags};use pinocchio::Address;// Create and set flags using individual methodslet mut flags = MemberFlags::new();flags.set(MemberFlags::AUTHORITY);flags.set(MemberFlags::TX_LOGS);flags.set(MemberFlags::TX_BALANCES);// Create a member with flagslet member = Member { flags, pubkey: user_address,};// Remove a flagflags.remove(MemberFlags::TX_LOGS);// Create flags from individual boolean valueslet flags = MemberFlags::from_acl_flags( true, // authority true, // tx_logs false, // tx_balances true, // tx_message false, // account_signatures);// Convert flags to byte valuelet flag_byte = flags.to_acl_flag_byte();// Create flags from byte valuelet flags = MemberFlags::from_acl_flag_byte(flag_byte);
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 ORconst flags = AUTHORITY_FLAG | TX_LOGS_FLAG;// Create a member with combined flagsconst member: Member = { flags, pubkey: new PublicKey(userAddress),};// Check if a flag is present using bitwise ANDconst 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 flagsconst updatedFlags = member.flags | TX_BALANCES_FLAG;// Remove a flag from existing flagsconst removedFlags = member.flags & ~TX_LOGS_FLAG;
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 ORconst flags = AUTHORITY_FLAG | TX_LOGS_FLAG | TX_BALANCES_FLAG;// Create a member with combined flagsconst member: Member = { flags, pubkey: userAddress,};// Use helper functions to check specific permissionsconst 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 memberconst updatedFlags = member.flags | TX_MESSAGE_FLAG;// Remove a flag from existing memberconst removedFlags = member.flags & ~TX_LOGS_FLAG;
The typical lifecycle of a permissioned account requires interaction with MagicBlock’s Permission Program ACLseoPoyC3cBqoUtkbjZ4aDrkurZW86v19pXz2XQnp1 and Delegation Program DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh:
Before making requests, verify TEE RPC integrity and obtain an authorization
token. Only members with appropriate flags can access or modify the account
state.
Once you’ve created your program, you can add permission and delegation hooks to control access to your accounts. For example, see Quickstart.Create a new permission account with initial members and their flags via MagicBlock’s Permission Program ACLseoPoyC3cBqoUtkbjZ4aDrkurZW86v19pXz2XQnp1.
Rust SDK
Pinocchio
Kit
Web3.js
use ephemeral_rollups_sdk::access_control::{ instructions::CreatePermissionCpiBuilder, structs::{ Member, MembersArgs, AUTHORITY_FLAG, // Member can directly modify the permission TX_LOGS_FLAG, // Member can view transaction logs TX_BALANCES_FLAG // Member can view account balances }};let members = Some(vec![ Member { // AUTHORITY_FLAG allows this member to modify permission settings // TX_LOGS_FLAG allows viewing transaction logs flags: AUTHORITY_FLAG | TX_LOGS_FLAG, pubkey: *payer.key, },]);// Note: Either the authority or permissioned_account can sign the transaction// The signer depends on who has AUTHORITY_FLAG in the members listCreatePermissionCpiBuilder::new(&permission_program) .permissioned_account(&permissioned_account) .permission(&permission) .payer(&payer) .system_program(&system_program) .args(MembersArgs { members }) .invoke_signed(&[seed_refs.as_slice()])?;
use ephemeral_rollups_pinocchio::instruction::create_permission;use ephemeral_rollups_pinocchio::types::{ Member, MemberFlags, MembersArgs,};use pinocchio::AccountView;// Create members with specific flags// AUTHORITY - Member can directly modify the permission// TX_LOGS - Member can view transaction logs// TX_BALANCES - Member can view account balances// TX_MESSAGE - Member can view transaction messages// ACCOUNT_SIGNATURES - Member can view account signatureslet mut flags = MemberFlags::new();flags.set(MemberFlags::AUTHORITY);flags.set(MemberFlags::TX_LOGS);let member = Member { flags, pubkey: payer_address,};let members = vec![member];let members_args = MembersArgs { members: Some(&members),};// Prepare accounts: [permissioned_account, permission, payer, system_program]let accounts: &[&AccountView] = &[ &permissioned_account, &permission, &payer, &system_program,];// Create the permission// Pass signer_seeds if permissioned_account is a PDA owned by your program// Pass None if permissioned_account is an on-curve signercreate_permission( accounts, &permission_program_id, members_args, pda_signer_seeds, // Or None if permissioned_account is on-curve)?;
import { createCreatePermissionInstruction, type Member, AUTHORITY_FLAG, // Member can directly modify the permission TX_LOGS_FLAG, // Member can view transaction logs TX_BALANCES_FLAG, // Member can view account balances} from "@magicblock-labs/ephemeral-rollups-sdk";import { transaction, sendAndConfirmTransaction } from "@solana/kit";const members: Member[] = [ { // AUTHORITY_FLAG allows this member to modify permission settings // TX_LOGS_FLAG allows viewing transaction logs flags: AUTHORITY_FLAG | TX_LOGS_FLAG, pubkey: payer.address, },];// Either the authority or permissioned_account can sign the transaction// The signer depends on who has AUTHORITY_FLAG in the members listconst createPermissionIx = await createCreatePermissionInstruction( { permissionedAccount: permissionedAccount.address, payer: payer.address, }, { members, });const tx = transaction([createPermissionIx]);const signature = await sendAndConfirmTransaction( client, tx, [payer]);console.log("TX:", signature);
import { Member, AUTHORITY_FLAG, // Member can directly modify the permission TX_LOGS_FLAG, // Member can view transaction logs TX_BALANCES_FLAG, // Member can view account balances createCreatePermissionInstruction,} from "@magicblock-labs/ephemeral-rollups-sdk";import { Transaction, sendAndConfirmTransaction } from "@solana/web3.js";const members: Member[] = [ { // AUTHORITY_FLAG allows this member to modify permission settings // TX_LOGS_FLAG allows viewing transaction logs flags: AUTHORITY_FLAG | TX_LOGS_FLAG, pubkey: payer.publicKey, },];// Either the authority or permissioned_account can sign the transaction// The signer depends on who has AUTHORITY_FLAG in the members listconst createPermissionIx = createCreatePermissionInstruction( { permissionedAccount: permissionedAccount.publicKey, payer: payer.publicKey, }, { members, });const tx = new Transaction().add(createPermissionIx);const txSig = await sendAndConfirmTransaction(connection, tx, [payer]);console.log("TX:", txSig);
Use Cases:
Initialize access control for a new delegated account
Delegate a permissioned account to enable low-latency Private Ephemeral Rollup execution via MagicBlock’s Delegation Program DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh.
These public validators are supported for development. Make sure to add the
specific ER validator in your delegation instruction:
Mainnet
Asia (as.magicblock.app):
MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57
EU (eu.magicblock.app):
MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e
US (us.magicblock.app):
MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd
TEE (mainnet-tee.magicblock.app):
MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo
Devnet
Asia (devnet-as.magicblock.app):
MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57
EU (devnet-eu.magicblock.app):
MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e
US (devnet-us.magicblock.app):
MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd
TEE (devnet-tee.magicblock.app):
MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo
Localnet
Local ER (localhost:7799):
mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev
Rust SDK
Pinocchio
Kit
Web3.js
use ephemeral_rollups_sdk::access_control::{ instructions::DelegatePermissionCpiBuilder};// Delegate the permission to enable low-latency Ephemeral Rollup execution// Either authority (with AUTHORITY_FLAG) or permissioned_account can authorizeDelegatePermissionCpiBuilder::new(&permission_program) .payer(&payer) // Pays for account creation .authority(&payer, false) // Authority signer (has AUTHORITY_FLAG) .permissioned_account(&permissioned_account, true) // or permissioned_account can sign .permission(&permission) .system_program(&system_program) .owner_program(&owner_program) .delegation_buffer(&delegation_buffer) .delegation_record(&delegation_record) .delegation_metadata(&delegation_metadata) .delegation_program(&delegation_program) .validator(&validator) // Validator that will execute the delegated state .invoke_signed(&[seed_refs.as_slice()])?;
use ephemeral_rollups_pinocchio::instruction::delegate_permission;use pinocchio::AccountView;pub fn process_delegate_permission( accounts: &[&AccountView], permission_program: &pinocchio::Address, authority_is_signer: bool, permissioned_account_is_signer: bool,) -> pinocchio::ProgramResult { // Accounts: [payer, authority, permissioned_account, permission, system_program, // owner_program, delegation_buffer, delegation_record, // delegation_metadata, delegation_program, validator (optional)] // Pass signer_seeds if permissioned_account is a PDA owned by your program delegate_permission( accounts, permission_program, authority_is_signer, permissioned_account_is_signer, signer_seeds, // Or None if permissioned_account is on-curve )?; Ok(())}
import { createDelegatePermissionInstruction, PERMISSION_PROGRAM_ID,} from "@magicblock-labs/ephemeral-rollups-sdk";import { transaction, sendAndConfirmTransaction } from "@solana/kit";// Delegate the permission to enable low-latency Ephemeral Rollup execution// Either authority (with AUTHORITY_FLAG) or permissioned_account can authorizeconst delegatePermissionIx = await createDelegatePermissionInstruction( { payer: payer.address, // Pays for account creation authority: [payer.address, true], // Authority signer (has AUTHORITY_FLAG) permissionedAccount: [permissionedAccount.address, false], // or permissioned_account can sign ownerProgram: PERMISSION_PROGRAM_ID, validator, // Validator that will execute the delegated state });const tx = transaction([delegatePermissionIx]);const signature = await sendAndConfirmTransaction( client, tx, [payer]);console.log("TX:", signature);
import { PERMISSION_PROGRAM_ID, createDelegatePermissionInstruction,} from "@magicblock-labs/ephemeral-rollups-sdk";import { Transaction, sendAndConfirmTransaction } from "@solana/web3.js";// Delegate the permission to enable low-latency Ephemeral Rollup execution// Either authority (with AUTHORITY_FLAG) or permissioned_account can authorizeconst delegatePermissionIx = createDelegatePermissionInstruction({ payer: payer.publicKey, // Pays for account creation authority: [payer.publicKey, true], // Authority signer (has AUTHORITY_FLAG) permissionedAccount: [permissionedAccount.publicKey, false], // or permissioned_account can sign ownerProgram: PERMISSION_PROGRAM_ID, validator, // Validator that will execute the delegated state});const tx = new Transaction().add(delegatePermissionIx);const txSig = await sendAndConfirmTransaction(connection, tx, [payer]);console.log("TX:", txSig);
Use Cases:
Enable ER execution for a permissioned account
Designate a specific validator to execute state changes
Set up delegation for real-time transaction processing
Important:
Either the authority or permissioned_account must sign based on AUTHORITY_FLAG
The validator processes transactions at ER speed
Commit frequency determines how often state syncs to Solana
Once delegated to Private Ephemeral Rollup, the permission is enforced - only members with appropriate flags can access or modify the account state
Modify existing members or their flags in a permission account via MagicBlock’s Permission Program ACLseoPoyC3cBqoUtkbjZ4aDrkurZW86v19pXz2XQnp1.
Rust SDK
Pinocchio
Kit
Web3.js
use ephemeral_rollups_sdk::access_control::{ instructions::UpdatePermissionCpiBuilder, structs::MembersArgs};// Update permission and modify members// Either authority or permissioned_account must sign based on AUTHORITY_FLAGUpdatePermissionCpiBuilder::new(&permission_program) .authority(&payer, true) // authority can sign if they have AUTHORITY_FLAG .permissioned_account(&permissioned_account, true) // or permissioned_account can sign .permission(&permission) // Setting members to None makes the permission public (temporarily visible) // This is useful for transitional states during delegation/undeligation .args(MembersArgs { members: None }) .invoke_signed(&[seed_refs.as_slice()])?;
use ephemeral_rollups_pinocchio::instruction::update_permission;use ephemeral_rollups_pinocchio::types::{ Member, MemberFlags, MembersArgs,};use pinocchio::AccountView;// Update permission with new member flags// Either authority or permissioned_account must sign based on AUTHORITY_FLAGlet mut new_flags = MemberFlags::new();new_flags.set(MemberFlags::TX_LOGS);new_flags.set(MemberFlags::TX_BALANCES);let updated_member = Member { flags: new_flags, pubkey: user_address,};let members = vec![updated_member];let members_args = MembersArgs { members: Some(&members),};// Prepare accounts: [authority, permissioned_account, permission]let accounts: &[&AccountView] = &[ &authority, &permissioned_account, &permission,];// Update the permission// Setting members to None makes the permission public (temporarily visible)// Pass signer_seeds if permissioned_account is a PDA owned by your programupdate_permission( accounts, &permission_program_id, authority.is_signer(), permissioned_account.is_signer(), members_args, signer_seeds, // Or None if permissioned_account is on-curve)?;
import { createUpdatePermissionInstruction, type Member,} from "@magicblock-labs/ephemeral-rollups-sdk";import { transaction, sendAndConfirmTransaction } from "@solana/kit";// Update permission and modify members// Either authority or permissioned_account must sign based on AUTHORITY_FLAGconst updatePermissionIx = await createUpdatePermissionInstruction( { authority: [payer.address, true], // authority can sign if they have AUTHORITY_FLAG permissionedAccount: [permissionedAccount.address, false], // or permissioned_account can sign }, { // Setting members to empty array or None makes the permission public (temporarily visible) // This is useful for transitional states during delegation/undeligation members: [], });const tx = transaction([updatePermissionIx]);const signature = await sendAndConfirmTransaction( client, tx, [payer]);console.log("TX:", signature);
import { createUpdatePermissionInstruction } from "@magicblock-labs/ephemeral-rollups-sdk";import { Transaction, sendAndConfirmTransaction } from "@solana/web3.js";// Update permission and modify members// Either authority or permissioned_account must sign based on AUTHORITY_FLAGconst updatePermissionIx = createUpdatePermissionInstruction( { authority: [payer.publicKey, true], // authority can sign if they have AUTHORITY_FLAG permissionedAccount: [permissionedAccount.publicKey, false], // or permissioned_account can sign }, { // Setting members to empty array or None makes the permission public (temporarily visible) // This is useful for transitional states during delegation/undeligation members: [], });const tx = new Transaction().add(updatePermissionIx);const txSig = await sendAndConfirmTransaction(connection, tx, [payer]);console.log("TX:", txSig);
Use Cases:
Add new members to a permission group
Revoke permissions by removing members
Change member flags to grant/restrict access
Set members to None to make account temporarily visible
When making requests to Private Ephemeral Rollup, you must first establish authorization:
Private Ephemeral Rollup (devnet) endpoint:
https://devnet-tee.magicblock.app?token= {authToken}. Replace {authToken} with your authorization token obtained
from the TEE RPC to send requests.
Authorization Steps:
Verify TEE RPC Integrity: Verify the TEE RPC server runs on genuine Intel TDX hardware using its TDX quote and Intel-issued attestation certificates
Request Authorization Token: Sign a challenge message to receive an authorization token
Create Connection: Pass the authorization token inside header or as query parameter
Kit
Web3.js
import { verifyTeeRpcIntegrity, getAuthToken,} from "@magicblock-labs/ephemeral-rollups-sdk";import { Connection } from "@magicblock-labs/ephemeral-rollups-kit";import * as nacl from "tweetnacl";const teeUrl = "https://devnet-tee.magicblock.app";const teeWsUrl = "wss://tee.magicblock.app";// 1. Verify the integrity of TEE RPCconst isVerified = await verifyTeeRpcIntegrity(teeUrl);// 2. Get AuthToken before making request to TEEconst authToken = await getAuthToken( teeUrl, userPubkey, (message: Uint8Array) => Promise.resolve(nacl.sign.detached(message, userSecretKey)),);// 3. Create connection with TEE using authorization tokenconst teeUserUrl = `${teeUrl}?token=${authToken.token}`;const teeUserWsUrl = `${teeWsUrl}?token=${authToken.token}`;const ephemeralConnection = await Connection.create(teeUserUrl, teeUserWsUrl);
import { verifyTeeRpcIntegrity, getAuthToken,} from "@magicblock-labs/ephemeral-rollups-sdk";import { Connection, Keypair } from "@solana/web3.js";import * as nacl from "tweetnacl";const teeUrl = "https://devnet-tee.magicblock.app";const teeWsUrl = "wss://tee.magicblock.app";// 1. Verify the integrity of TEE RPCconst isVerified = await verifyTeeRpcIntegrity(teeUrl);// 2. Get AuthToken before making request to TEEconst authToken = await getAuthToken( teeUrl, userKeypair.publicKey, (message: Uint8Array) => Promise.resolve(nacl.sign.detached(message, userKeypair.secretKey)),);// 3. Create connection with TEEconst teeUserUrl = `${teeUrl}?token=${authToken.token}`;const teeUserWsUrl = `${teeWsUrl}?token=${authToken.token}`;const connection = new Connection(teeUserUrl, { wsEndpoint: teeUserWsUrl,});
Account Visibility:
By default: All accounts are transparent and accessible to any authorized user
With permissions: Once an account is permissioned through the permission system, access control rules are enforced during every request
Only members with appropriate flags can access or modify permissioned accounts and transactions
Key Points:
Requests are executed in real-time on the validator
Member flags determine what each user can do
Permissions can be updated dynamically during execution
Updates take effect immediately on the Private Ephemeral Rollup
The latest permission state on Private Ephemeral Rollup enforces access control. For closing permission account, update permission members before undelegating permission to Solana.
use ephemeral_rollups_pinocchio::instruction::commit_and_undelegate_permission;use pinocchio::AccountView;pub fn process_commit_and_undelegate_permission( accounts: &[&AccountView], permission_program: &pinocchio::Address, authority_is_signer: bool, permissioned_account_is_signer: bool,) -> pinocchio::ProgramResult { // Accounts: [authority, permissioned_account, permission, magic_program, magic_context] // Pass signer_seeds if permissioned_account is a PDA owned by your program commit_and_undelegate_permission( accounts, permission_program, authority_is_signer, permissioned_account_is_signer, signer_seeds, // Or None if permissioned_account is on-curve )?; Ok(())}
Close a permissioned account and reclaim its lamports on Solana.
Rust SDK
Pinocchio
Kit
Web3.js
use ephemeral_rollups_sdk::access_control::{ instructions::ClosePermissionCpiBuilder};// Close the permission account and reclaim lamports// IMPORTANT: The permission must be undelegated to Solana first// If delegated, call commit_and_undelegate before closingClosePermissionCpiBuilder::new(&permission_program) .payer(&payer) // Receives reclaimed lamports .authority(&payer, false) // Authority signer (has AUTHORITY_FLAG) .permissioned_account(&permissioned_account, true) // or permissioned_account can sign .permission(&permission) // The permission account to close .invoke_signed(&[seed_refs.as_slice()])?;
use ephemeral_rollups_pinocchio::instruction::close_permission;use pinocchio::AccountView;pub fn process_close_permission( accounts: &[&AccountView], permission_program: &pinocchio::Address, authority_is_signer: bool, permissioned_account_is_signer: bool,) -> pinocchio::ProgramResult { // Accounts: [payer, authority, permissioned_account, permission] // IMPORTANT: The permission must be undelegated to Solana first // If the permission is still delegated, this operation will fail // Pass signer_seeds if permissioned_account is a PDA owned by your program close_permission( accounts, permission_program, authority_is_signer, permissioned_account_is_signer, signer_seeds, // Or None if permissioned_account is on-curve )?; // The permission account lamports are transferred to the payer Ok(())}
import { createClosePermissionInstruction,} from "@magicblock-labs/ephemeral-rollups-sdk";import { transaction, sendAndConfirmTransaction } from "@solana/kit";// Close the permission account and reclaim lamports// IMPORTANT: The permission must be undelegated to Solana first// If delegated, call commit_and_undelegate before closingconst closePermissionIx = await createClosePermissionInstruction({ payer: payer.address, // Receives reclaimed lamports authority: [payer.address, true], // Authority signer (has AUTHORITY_FLAG) permissionedAccount: [permissionedAccount.address, false], // or permissioned_account can sign});const tx = transaction([closePermissionIx]);const signature = await sendAndConfirmTransaction( client, tx, [payer]);console.log("TX:", signature);
import { createClosePermissionInstruction } from "@magicblock-labs/ephemeral-rollups-sdk";import { Transaction, sendAndConfirmTransaction } from "@solana/web3.js";// Close the permission account and reclaim lamports// IMPORTANT: The permission must be undelegated to Solana first// If delegated, call commit_and_undelegate before closingconst closePermissionIx = createClosePermissionInstruction({ payer: payer.publicKey, // Receives reclaimed lamports authority: [payer.publicKey, true], // Authority signer (has AUTHORITY_FLAG) permissionedAccount: [permissionedAccount.publicKey, false], // or permissioned_account can sign});const tx = new Transaction().add(closePermissionIx);const txSig = await sendAndConfirmTransaction(connection, tx, [payer]);console.log("TX:", txSig);
Use Cases:
Clean up unused permission accounts
Reclaim SOL from closed accounts
Important:
The permission must be undelegated to Solana first
If delegated, call commit_and_undelegate before closing
Authority Management: Always assign AUTHORITY_FLAG to at least one trusted member
Least Privilege: Grant only necessary flags to each member
Real-time Updates: Permissions can be updated in real-time on Private Ephemeral Rollup without undelegating, allowing dynamic access control adjustments
Cleanup: Undelegate and close unused permission accounts to free SOL
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