Asset Agents & Permissions
Overview
Issuers of regulated securities often delegate specific responsibilities, like managing documentation or compliance, to specialized service providers or internal teams. Polymesh facilitates this through agents and agent groups, providing granular control over permissions related to a specific asset.
The externalAgents pallet, in conjunction with the identity pallet, provides the necessary functionality to manage the entire lifecycle of agent permissions for an asset.
Key Concepts
Agents
An agent is any Polymesh Identity that has been granted specific permissions to act on behalf of an asset. This includes the asset issuer, who is automatically designated as an agent with Full permissions upon asset creation, as well as any additional identities (such as service providers or internal staff) that are granted agent status.
Agent Groups
Permissions are not assigned directly to agents but rather to Agent Groups. Each agent assigned to an asset belongs to exactly one group for that asset. An asset can have multiple agent groups, each defining a different set of permissions.
Permissions
Polymesh employs a hierarchical permission system allowing fine-grained control. Permissions determine which actions (extrinsics within specific pallets) an agent belonging to a group can perform for the asset.
Permissions can be defined at the group level using two types:
- Full: Grants access to all extrinsics across all pallets related to the asset.
- These: Grants access only to specified extrinsics within specified pallets.
This allows for precise delegation, such as granting an agent permission only to manage asset documentation (asset::add_documents, asset::remove_documents) but nothing else.
A third form, Except — everything but a named set — exists in the ExtrinsicPermissions type but is not accepted for agent groups. See Why Except is refused.
Permission Groups
Predefined Groups
Polymesh offers several built-in agent groups for common use cases:
| Group Name | Description |
|---|---|
Full | Grants all permissions for the asset. |
ExceptMeta | Grants all permissions except those related to managing agent identities and permissions (externalAgents pallet). |
PolymeshV1CAA | Grants permission to all extrinsics in the CorporateAction, CorporateBallot and CapitalDistribution pallets |
PolymeshV1PIA | Grants permission to the issue, redeem and controller_transfer extrinsics from the Asset pallet and all extrinsics in the STO pallet, except sto::invest |
ExceptMeta withholds agent management, but it does not withhold ownership transfer. Because it excludes only the ExternalAgents pallet, an ExceptMeta agent is still permitted to call asset::accept_asset_ownership_transfer. Such an agent cannot add or remove agents directly, but can transfer ownership of the asset to another identity, which indirectly hands every agent right to the new owner.
Custom Groups
Custom agent groups allow issuers to define highly specific permissions for agents on a per-asset basis.
Creating a Custom Agent Group
Custom agent groups are initially created using the externalAgents::create_group method.
Parameters:
asset_id: The AssetId (UUID) of the asset.perms: The permissions for the group, defined as anExtrinsicPermissionsstructure (see below).
Effects:
- Creates a new custom agent group for the asset, assigning it a unique group ID (
AGId). The group ID is used when inviting or assigning agents.
Errors:
- Creating a permission group can fail if the caller is not authorized or if the permissions structure is too large.
Defining Permissions: ExtrinsicPermissions Structure
The perms parameter uses the ExtrinsicPermissions structure, which allows for fine-grained control over what actions an agent group can perform. Permissions are defined in two layers:
- Pallet Level: Specify which pallets the group can interact with.
- Extrinsic Level: For each pallet, specify which extrinsics (methods) are allowed or denied.
At both levels, you can use:
Whole: All pallets or all extrinsics in a pallet.These: Only the specified pallets or extrinsics.
Example:
- To allow only documentation management, set
permsto allow only theAssetpallet and the pallet'sadd_documentsandremove_documentsextrinsics:- At the pallet level:
These([Asset]) - At the extrinsic level for
Asset:These([add_documents, remove_documents])
- At the pallet level:
When defining custom agent permissions, pallet names use UpperCamelCase (e.g., Asset), and extrinsic names use snake_case (e.g., add_documents). The chain metadata can be used to identify supported pallets and functions. Subscan offers a convenient tool for exploring the chain metadata to identify correct pallet and extrinsic names.
Why Except is refused
The ExtrinsicPermissions type has a third variant, Except, meaning "everything but these". It is rejected when setting agent group permissions, at both the pallet level and the extrinsic level within a pallet.
The reason is that an Except permission silently widens. It is defined against the extrinsics that exist when it is written, so a chain upgrade that adds an extrinsic grants it to every agent in that group without anyone deciding to. A permission set meant to withhold asset::issue would, after an upgrade adding a new issuance call, grant that call too. These cannot widen this way: a new extrinsic is simply not in the list.
The chain enforces this:
Where Except appears | Error |
|---|---|
At the pallet level — Except([Asset]) | externalAgents::ExceptPermissionsNotAllowed |
Nested inside a These entry — These([Asset: Except([issue])]) | identity::ExceptNotAllowedForExtrinsics |
Both apply to externalAgents::create_group and externalAgents::set_group_permissions, and the same rule governs secondary key permissions. Enumerate the calls you intend to allow with These instead.
The check runs only when permissions are set, so a group created before this rule took effect keeps whatever it was given and continues to work. It is re-checked the next time someone calls set_group_permissions on it, which will fail until the Except is replaced with an explicit These set.
Except remains valid for asset and portfolio permissions, which name concrete on-chain entities rather than extrinsics and so cannot widen through a runtime upgrade.
Carefully consider whether to grant access to the ExternalAgents pallet when defining custom group permissions. Granting permission to this pallet allows agents to modify agent groups and permissions including their own. To prevent agents from escalating their privileges or altering group membership, it is recommended to not include the ExternalAgents pallet unless such administrative control is intended.
Carefully consider whether to grant the Asset pallet's accept_asset_ownership_transfer extrinsic. An agent permitted to call it can create a TransferAssetOwnership authorization that hands the entire asset to another identity.
This permission is easy to grant unintentionally. A group granted the whole Asset pallet — a natural shape for an "asset manager" group — includes it, as does any Except form at either the pallet or extrinsic level that does not explicitly exclude it. This is distinct from the forward-compatibility concern above: the extrinsic exists today, and is included the moment the group's permissions are broad enough to cover it.
To withhold it, enumerate the permitted extrinsics with These, or add accept_asset_ownership_transfer to an Except list. See Ownership Transfers for how the permission is evaluated.
Modifying Custom Groups
- Use
externalAgents::set_group_permissions(asset_id, id, perms)to update the permissions for an existing custom group. This method is identical to thecreate_groupmethod described above but takes an additionalidparameter for the agent group too be modified.
Agent Lifecycle
Managing agents involves a multi-step process at the chain level:
1. Invitation
An existing agent with permission to manage agents invites a new agent by creating an authorization request using the identity::addAuthorization extrinsic (from the identity pallet), with AuthorizationData::BecomeAgent specifying the asset and agent group.
2. Acceptance
The invited agent must accept the authorization using the externalAgents::accept_become_agent extrinsic, providing the authorization ID. This links the agent to the specified group for the asset.
- If the group's permissions are later changed, the agent's permissions update automatically.
- The agent can reject the authorization using
identity::removeAuthorization.
3. Changing Group
An agent's group can be changed by an authorized agent using the externalAgents::change_group extrinsic.
4. Abdication (Self-Removal)
An agent can remove themselves from an asset using the externalAgents::abdicate extrinsic. The last Full agent cannot abdicate until another Full agent is added.
5. Removal
An authorized agent can remove another agent using the externalAgents::remove_agent extrinsic. The last Full agent cannot be removed.
Default Permissions
When an Identity creates an asset, that Identity is automatically assigned to the Full permission group for that asset as an agent.
Ownership Transfer Considerations
Agent permissions are tied to the asset, not the asset owner's Identity. When asset ownership (control) is transferred to a new Identity, the previous owner is removed as an agent and the new owner is added as a Full agent automatically. Other agents and their permissions remain unchanged, so managing agent permissions is still a key step during ownership changes.
Agents are not only affected by an ownership transfer. A sufficiently permissioned agent can also initiate one. Any agent whose group grants asset::accept_asset_ownership_transfer, including all Full and ExceptMeta agents, can create the authorization that transfers the asset. See Ownership Transfers for the full details of which identities qualify and when the check is applied.
SDK Integration
The Polymesh SDK provides convenient methods to interact with the chain extrinsics and storage described above. For example, the SDK's Permissions class wraps calls to externalAgents::create_group, externalAgents::remove_agent, and related extrinsics, and provides helpers for querying agent groups and permissions. See the SDK documentation for details.
Permissions (Asset Side)
The Permissions class (accessed via asset.permissions) provides methods for managing and querying an asset agent roles and permissions across a asset:
inviteAgent: Invites an Identity to become an agent for the asset within a specific group. Handles theidentity::addAuthorizationcall.removeAgent: Removes an agent associated with the asset. Handles theexternalAgents::removeAgentcall.createGroup: Creates a new custom permission group. Handles theexternalAgents::createGroupcall.getAgents: Retrieves a list of agents and their associated groups for the asset.getGroup: Retrieves details for a specific permission group.getGroups: Retrieves all permission groups associated with the asset.
AssetPermissions (Identity Side)
The AssetPermissions class provides methods for managing and querying an Identity's agent roles and permissions across assets:
get: Retrieve all assets over which this Identity has agent permissions, with the corresponding permission group.getGroup: Retrieve this Identity's permission group for a specific asset.checkPermissions: Check whether this Identity has specific transaction permissions over an asset.setGroup: Assign this Identity to a different permission group for a given asset.waive: Abdicate from the current permission group for a given asset (removes all permissions for that asset).
Acceptance of an authorization request to become an agent is done with the Authorizations (accessed via identity.authorizations).
Summary
Polymesh's agent system provides a robust and granular framework for delegating asset management responsibilities. By leveraging predefined and custom agent groups, issuers can securely grant specific permissions to third parties or internal teams, ensuring compliance and operational efficiency while adhering to the principle of least privilege. The two-stage invitation/acceptance process adds a layer of security, requiring explicit consent from the designated agent.