Base Protocol

Beryl / B20

B20 is Base's native standard for compliant tokenized assets — enforced by a Rust precompile in the node, not EVM bytecode. Activated by the Beryl upgrade on June 25, 2026.

What is B20?

B20 (Base Standard 20) is a tokenization primitive built directly into the Base node as a Rust precompile. This means compliance rules — pause, policy gating, supply caps — are enforced at the node level and cannot be bypassed by EVM bytecode.

B20 is not a smart contract standard like ERC-20. It is an extension of the Base execution environment itself, activated by the Beryl network upgrade.

Beryl Activation
Mainnet: June 25, 2026 at 18:00 UTC. Base Sepolia: already active. Use the B20 Hub to inspect tokens, check roles, and browse the on-chain registry.

Two Variants

ASSET
For tokenized real-world assets — stocks, commodities, real estate. Supports rebase via a multiplier() view function.
STABLECOIN
For fiat-backed stable assets. Has a currency() field (e.g. "USD") to declare the peg.

Key Addresses

All precompile addresses are the same on both mainnet and Sepolia.

Base precompile addresses
B20Factory        0xB20f000000000000000000000000000000000000
PolicyRegistry    0x8453000000000000000000000000000000000002
ActivationRegistry 0x8453000000000000000000000000000000000001

Deploying a B20 Token

Tokens are created through the B20Factory. The factory emits a B20Created event for each deploy.

Create a B20 tokenSolidity
// ASSET variant (variant = 0)
IB20Factory factory = IB20Factory(0xB20f000000000000000000000000000000000000);
address token = factory.createB20(
  "My Token",     // name
  "MTK",          // symbol
  18,             // decimals
  0,              // variant: 0 = ASSET, 1 = STABLECOIN
  ""              // variantParams (abi-encoded extra data)
);

Policy System

B20 supports exactly two policy types: ALLOWLIST and BLOCKLIST. Policies are managed through the PolicyRegistry precompile and assigned to one of four scopes per token.

TRANSFER_SENDER_POLICYGoverns who can send tokens.
TRANSFER_RECEIVER_POLICYGoverns who can receive tokens.
TRANSFER_EXECUTOR_POLICYGoverns who can call transferFrom().
MINT_RECEIVER_POLICYGoverns who can receive newly minted tokens.

policyId = 0 means ALWAYS_ALLOW (no restriction). To restrict a scope:

Create + apply a policySolidity
IPolicyRegistry pReg = IPolicyRegistry(0x8453000000000000000000000000000000000002);

// 1. Create the policy (returns a uint64 policyId)
uint64 id = pReg.createPolicy(adminAddress, IPolicyRegistry.PolicyType.ALLOWLIST);

// 2. Add authorized addresses
pReg.addToPolicy(id, allowedAddress);

// 3. Apply it to the token scope
token.updatePolicy(token.TRANSFER_RECEIVER_POLICY(), id);
Common mistakes
There is no registerPolicy() function. Call createPolicy(admin, PolicyType) on the PolicyRegistry to get a policyId, then apply it with token.updatePolicy(scope, policyId). Freeze-seize (burnBlocked()) and supply caps (updateSupplyCap()) are role-gated, not policy types.

7 Core Roles

B20 uses OpenZeppelin AccessControl internally but omits AccessControlEnumerable — role holders cannot be enumerated. Use hasRole(role, address) to check specific accounts (see the B20 Hub → Roles tab).

DEFAULT_ADMIN_ROLEManages all other roles. Sets supply cap via updateSupplyCap().
MINT_ROLECan mint new tokens to any address.
BURN_ROLECan burn tokens (holder must approve or consent).
BURN_BLOCKED_ROLEFreeze-seize: can call burnBlocked(from, amount) to confiscate and burn tokens.
PAUSE_ROLECan pause TRANSFER, MINT, or BURN features independently.
UNPAUSE_ROLECan unpause any paused feature.
METADATA_ROLECan update token name, symbol, and other metadata.

Supply Cap

B20 tokens have an on-chain supply cap enforced at the node level. The sentinel value type(uint128).max means uncapped. Call token.supplyCap() to read the current cap. Update it with token.updateSupplyCap(newCap) — requires DEFAULT_ADMIN_ROLE.

Pause / Unpause

Transfers, minting, and burns can each be paused independently. Call token.isPaused(PausableFeature.TRANSFER) to check. The Scanner tab in the B20 Hub shows live pause status for any token.

Freeze-Seize (burnBlocked)

The BURN_BLOCKED_ROLE grants the ability to forcibly confiscate and burn tokens from any address. This is distinct from policy blocking (which prevents transfers) — burnBlocked permanently removes tokens from circulation. It requires explicit regulatory authority and is designed for asset recovery in regulated markets.

Freeze-seizeSolidity
// Requires BURN_BLOCKED_ROLE
token.burnBlocked(holderAddress, amount);

On-Chain Inspector

Use the B20 Hub to inspect any token live — zero LLM, all data from multicall. Check scanner results, role holders, the on-chain registry, and simulate transfers before they happen.

Deploy B20 via Blue Chat
In Blue Chat, type deploy a B20 asset token named "My Token" symbol "MTK" — the agent will generate and sign a createB20 Factory transaction directly.

How the B20 scanner works

Every result in the B20 Hub Scanner is read live from Base RPC via multicall — zero LLM, zero guessing. The numbers and flags come straight from on-chain state, never from a model.

Inspection flow

The scanner reads each token in a fixed, deterministic sequence:

  1. 1.Validate the address format.
  2. 2.Check isB20 against the B20Factory precompile — confirm it is a real B20, not arbitrary EVM bytecode.
  3. 3.Read core state: name, symbol, decimals, total supply, supply cap, and variant.
  4. 4.Read pause status per feature: transfer, mint, and burn.
  5. 5.Read the policy ID per scope: transfer sender, transfer receiver, transfer executor, and mint receiver.
  6. 6.Read variant detail: the rebase multiplier (Asset) or the currency code (Stablecoin).

Trust verdict — deterministic, not a score

We surface concrete flags, never a single pass/fail number that would overstate certainty. The verdict is computed in code from the reads above, so the same on-chain state always yields the same flags.

!Transfers, mint, or burn are paused — the issuer can freeze that operation.
!A transfer or mint scope is policy-gated by an allowlist or blocklist, not open.
!Supply is uncapped — the issuer can mint without limit.
No pauses, no restrictive policies, and a capped supply — no issuer-side transfer restrictions detected at read time.

Limitations

  • B20 omits AccessControlEnumerable, so role holders cannot be listed — each role is only checked per wallet via hasRole.
  • Reads reflect on-chain state at the moment of the scan. Roles and policies can change afterward.
  • Advisory only — verify independently before trusting or trading a token.