Permission Program
链上权限管理(即将推出)
Ephemeral Rollups SDK
用于 Private Ephemeral Rollups 的 SDK
概述
Private Ephemeral Rollups 是 Ephemeral Rollups 的一种形式,它在以合规为核心的 Trusted Execution Environment 中,为受权限控制的账户提供细粒度权限管理。每个权限账户都会维护一份成员列表,并通过特定标志来决定这些成员可以执行哪些操作。核心概念
- Permission Account:用于存储特定账户访问控制规则的 PDA
- Members:通过标志授予特定权限的地址
- Flags:用于定义成员可执行操作的位掩码(authority、查看日志、查看余额等)
- Public Permissions:当成员被设置为
None时,受权限控制的账户会暂时变为可见
成员标志
成员标志为每位成员定义细粒度权限。你可以通过按位 OR 组合多个标志,以授予多个权限。 标志说明:- AUTHORITY:允许成员更新和委托权限设置、添加或移除其他成员,以及更新成员标志。
- TX_LOGS:允许成员查看交易执行日志。
- TX_BALANCES:允许成员查看账户余额变更。
- TX_MESSAGE:允许成员查看交易消息数据。
- 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 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
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);
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;
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;
Ephemeral Permission
EphemeralPermission 账户完全运行在 Ephemeral Rollup 上,由已委托的 PDA
承担租金——无需在 Base Layer 上创建、委托或 commit-and-undelegate
任何权限账户。三个 CPI 操作即可覆盖完整生命周期:Create、Update、Close,
全部由 ER 上的数据账户作为 PDA 签名,通过 MagicBlock 的 Permission Program
ACLseoPoyC3cBqoUtkbjZ4aDrkurZW86v19pXz2XQnp1 调用。DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh)。委托完成后,PDA 会使用其程序种子
在 ER 上签署全部三个权限操作,并支付 ephemeral permission 的租金——因此必须在
initialize 时预先充值。完整流程请参阅
快速开始。
这些公共验证器可用于开发环境。请确保在你的委托指令中添加对应的 ER 验证器:
主网- 亚洲 (as.magicblock.app):
MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57 - 欧盟 (eu.magicblock.app):
MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e - 美国 (us.magicblock.app):
MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd - TEE (mainnet-tee.magicblock.app):
MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo
- 亚洲 (devnet-as.magicblock.app):
MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57 - 欧盟 (devnet-eu.magicblock.app):
MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e - 美国 (devnet-us.magicblock.app):
MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd - TEE (devnet-tee.magicblock.app):
MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo
- 本地 ER (localhost:7799):
mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev
在 ER 上初始化一个带有初始成员和隐私标志的
EphemeralPermission 账户。
幂等——如果已存在则跳过。切换隐私标志,或添加 / 移除 / 更新成员。更新会立即在 ER 上生效。
当不再需要权限时,关闭 ER 上的
EphemeralPermission 账户,
并将租金退还给数据 PDA。Ephemeral Permission 操作
- 1. 创建 Ephemeral Permission
- 2. 更新 Ephemeral Permission
- 3. 关闭 Ephemeral Permission
通过 使用场景:
CreateEphemeralPermissionCpi 在 ER 上初始化一个新的 EphemeralPermission
账户。Payer = 已委托的数据 PDA,它使用其程序种子签名,并使用 initialize
时预先充值的 lamports 来支付租金。- Anchor
- Rust SDK
- Pinocchio
- Kit
- Web3.js
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])?;
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])?;
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])?;
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);
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);
- 为 ER 上新委托的 PDA 启动访问控制
- 以公开模式启动(
is_private: false、成员为空),后续通过 Update 收紧
通过 使用场景:
UpdateEphemeralPermissionCpi 切换隐私标志并重写成员列表。每次调用都
重新构建完整成员列表(包含 authority),以确保数据 PDA 永远不会把自己锁在外面。- Anchor
- Rust SDK
- Pinocchio
- Kit
- Web3.js
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])?;
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])?;
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])?;
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);
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);
- 按需切换
is_private(如:私密游玩 / 公开揭示) - 添加新的查看者,并设置
TX_LOGS | TX_MESSAGE | TX_BALANCES标志 - 通过在下一次调用的成员列表中省略某个成员来撤销其权限
通过 使用场景:
CloseEphemeralPermissionCpi 关闭 ER 上的 EphemeralPermission 账户。
租金会退还给数据 PDA(原始 payer)。可选——仅在不再需要权限时调用。- Anchor
- Rust SDK
- Pinocchio
- Kit
- Web3.js
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])?;
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])?;
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])?;
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);
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);
- 在取消委托数据 PDA 之前回收 ephemeral 租金
- 私密执行结束后拆除访问控制
参考实现:
private-counter
(Anchor)和
pinocchio-private-counter。最佳实践
- Authority 管理:始终至少为一位可信成员分配
AUTHORITY_FLAG - 最小权限原则:仅向每位成员授予必要的标志
- 实时更新:权限可以在不取消委托的情况下于 Private Ephemeral Rollup 上实时更新,从而实现动态访问控制调整
- 清理:取消委托并关闭未使用的权限账户,以释放 SOL
安全注意事项
- 签名者验证:只有拥有
AUTHORITY_FLAG的成员或带有受权限控制账户的程序才能授权变更 - 公开账户:将成员设置为
None会使账户对外公开可见 - 默认 Authority:默认情况下,受权限控制账户的所有者会被添加为权限账户成员中的 permission authority
- 空成员列表:如果
members字段被设置为空列表,则受权限控制账户将完全受限且保持私密。只有该账户所有者才能修改权限 - 访问审计:使用成员标志来审计和控制访问
访问控制
细粒度访问控制
链上隐私
隐私机制与核心概念
授权
授权框架
合规框架
合规标准与指南

