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.
Two Variants
multiplier() view function.currency() field (e.g. "USD") to declare the peg.Key Addresses
All precompile addresses are the same on both mainnet and Sepolia.
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).
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:
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);
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.
// 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 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:
{
"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.Ask b20_check_activation for your target chain — createB20 reverts until B20 is active there.
- 2.Ask b20_encode_deploy with name, symbol, variant, and your admin wallet address.
- 3.Sign the returned { to, data, value } in your wallet (EIP-5792 send_calls / Base MCP).
- 4.After deploy, use b20_encode_mint / b20_encode_payment for ongoing operations.
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.Validate the address format.
- 2.Check isB20 against the B20Factory precompile — confirm it is a real B20, not arbitrary EVM bytecode.
- 3.Read core state: name, symbol, decimals, total supply, supply cap, and variant.
- 4.Read pause status per feature: transfer, mint, and burn.
- 5.Read the policy ID per scope: transfer sender, transfer receiver, transfer executor, and mint receiver.
- 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.
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.