Skip to main content
A B20 token chooses policies by scope. Each scope stores one uint64 policy ID that points to a policy in the singleton PolicyRegistry. The token only checks the policy assigned to the relevant scope.
B20 policies and scopes diagram: fixed token policy scopes store uint64 pointers to PolicyRegistry policies, and gated operations call isAuthorized before continuing or reverting.
On a gated operation, the token reads the relevant scope, calls PolicyRegistry.isAuthorized(policyId, account), and reverts with PolicyForbids or a seize-specific error when the policy result does not permit the action.

Policy scopes

approve and permit are not policy-gated. Only balance movement is checked.
Every scope defaults to ALWAYS_ALLOW (0) at creation. An unattended B20 deployment is fully open, and no account is seizable until SEIZE_HOLDER_POLICY is intentionally configured.

Policy types

Composite policies combine existing simple ALLOWLIST and BLOCKLIST policies. Child policies must be simple policies, not other composites or built-ins.

Built-ins and ID anatomy

Policy IDs are uint64 values. The top byte is the PolicyType; the low 56 bits are a global counter. Counters 0 and 1 are reserved for built-ins, and custom policy creation starts at counter 2.
isAuthorized never reverts on a non-existent policy ID. Malformed or uncreated IDs collapse to empty-set semantics: ALLOWLIST denies and BLOCKLIST allows. Validate policyExists(policyId) before binding a token scope.

Registry administration

Each policy has one admin. The current admin can:
  • Update allowlist, blocklist, or composite membership.
  • Stage a two-step admin transfer with stageUpdateAdmin.
  • Permanently renounce policy administration with renounceAdmin.
renounceAdmin(policyId) freezes the policy forever. Membership and child-policy updates become impossible.

Read a token’s configured policies

Configure policies in code

IPolicyRegistry reference