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. Enabled by the Beryl upgrade, with mainnet activation scheduled for July 8, 2026 (the exact go-live is gated on-chain by the ActivationRegistry).

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: scheduled July 8, 2026 (exact go-live gated on-chain by the ActivationRegistry). 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 takes a variant enum, a salt, abi-encoded params, and an array of initCalls that run atomically at deploy (e.g. grantRole, updateSupplyCap, seed mint).

B20Factory.createB20 signatureSolidity
IB20Factory factory = IB20Factory(0xB20f000000000000000000000000000000000000);

// variant: 0 = ASSET (params encode decimals), 1 = STABLECOIN (params encode currency)
address token = factory.createB20(
  uint8   variant,     // 0 = ASSET, 1 = STABLECOIN
  bytes32 salt,        // deterministic deploy salt
  bytes   params,      // abi-encoded (version, name, symbol, initialAdmin, decimals|currency)
  bytes[] initCalls    // atomic setup: grantRole, updateSupplyCap, mint...
);

Encoding params and initCalls by hand is error-prone. The b20_encode_deploy MCP tool (see Deploy from Claude / Cursor below) builds the full calldata for you and returns a ready-to-sign { to, data, value }.

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.

Deploy from Claude / Cursor (MCP)

Blue Agent exposes B20 as MCP tools, so you can deploy, mint, grant roles, and take payments straight from Claude Code, Claude Desktop, or Cursor. Point your client at the remote server — nothing to install:

Claude Code / Cursor / Desktop configMCP
{
  "mcpServers": {
    "blue-agent": {
      "url": "https://blueagent.dev/api/mcp"
    }
  }
}

The B20 tools are non-custodial — they never touch your keys and never charge a payment. The encoders return a { to, data, value } that you sign in your own wallet (via EIP-5792 send_calls or the Base MCP); b20_check_activation and b20_read_token are read-only on-chain queries. Blue Agent goes factory-direct — no platform fee is added.

b20_check_activationRead live whether ASSET / STABLECOIN are active on mainnet or Sepolia (ActivationRegistry).
b20_read_tokenInspect a token live via multicall — isB20, supply, cap, variant, pause + policy gating (zero LLM).
b20_encode_deployEncode createB20 — name, symbol, variant, admin, optional decimals / supply cap / seed mint.
b20_encode_mintEncode mint (or mintWithMemo) on an existing token. Signer must hold MINT_ROLE.
b20_encode_burnEncode burnWithMemo — burn from the caller's balance with a memo. Signer must hold BURN_ROLE.
b20_encode_grant_mint_roleEncode grantRole(MINT_ROLE, account). Signer must hold DEFAULT_ADMIN_ROLE.
b20_encode_paymentEncode transferWithMemo — pay with an on-chain memo / order id for reconciliation.

A typical non-custodial deploy flow from your MCP client:

  1. 1.Ask b20_check_activation for your target chain — createB20 reverts until B20 is active there.
  2. 2.Ask b20_encode_deploy with name, symbol, variant, and your admin wallet address.
  3. 3.Sign the returned { to, data, value } in your wallet (EIP-5792 send_calls / Base MCP).
  4. 4.After deploy, use b20_encode_mint / b20_encode_payment for ongoing operations.
More MCP tools
See the MCP Setup page for the full tool catalog — console commands, Hub tools, and the B20 builders — plus client-specific setup notes.

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.