Skip to main content
Per-hardfork, per-feature migration notes for the B20 token standard. Each Cobalt entry is a focused, code-forward changelog for one scoped feature change that crosses a hardfork boundary. Newest first.
B20 method and event signatures are part of the chain’s consensus surface. Existing selectors and behavior do not change; the standard grows by addition.

Hardfork Ordinals

Cobalt (Upcoming) — Ordinal 02

Beryl — Initial Release

Shipped with the Beryl upgrade. Added
  • The B20 standard as a native precompile: a superset of ERC-20 with full selector and behavior parity for transfer, transferFrom, approve, allowance, balanceOf, totalSupply, name, symbol, decimals, Transfer, and Approval
  • Role-based access control with built-in roles, user-defined roles in the role graph, and renounceLastAdmin for permanent admin-less operation
  • PolicyRegistry singleton precompile with BLOCKLIST, ALLOWLIST, UNION, and INTERSECT policy types, two-step admin transfer, and the ALWAYS_ALLOW and ALWAYS_BLOCK built-ins
  • Six policy scopes on every token, covering transfer sender, receiver, and executor, mint receiver, and seize holder and receiver
  • seizeWithMemo for compliance-driven balance transfers
  • Memo variants of transfer, transferFrom, mint, burn, and seize, emitting Memo immediately after the primary event
  • Granular pausing by PausableFeature: TRANSFER, MINT, BURN, and SEIZE
  • Optional supply caps, with type(uint128).max as the uncapped sentinel and maximum supply
  • ERC-2612 permit with an EIP-712 domain at version "1"
  • ERC-7572 contractURI and METADATA_ROLE-gated name, symbol, and URI updates
  • B20Factory singleton precompile with deterministic, variant-encoding addresses and initCalls bootstrap semantics
  • ActivationRegistry gating for state-changing PolicyRegistry calls
  • The ASSET variant: OPERATOR_ROLE, WAD-precision UI multipliers with scheduled and instant updates, announcements, batchMint, and issuer-defined extra metadata
  • The STABLECOIN variant: fixed 6 decimals and a currency() code set once at creation
Deprecated
  • burnBlocked and BURN_BLOCKED_ROLE, retained for backwards compatibility. New seizure flows use seizeWithMemo.
Pooled-balance integrators: seizeWithMemo and burnBlocked both allow funds to move out of a contract address without the regular transfer flow. This affects lending-protocol vaults, AMM pools, staking contracts, custodial wallets, and bridges — any system that maintains internal accounting against a single on-chain token balance. The mechanism acts at the pooling contract’s address, not at individual depositor-share granularity, so the accounting impact falls on the pool as a whole.This is not a new risk. burnBlocked already allowed an issuer to zero a blocked address’s balance through block, burn, and reissue elsewhere. seizeWithMemo does not expand who is exposed — it collapses that workaround into one call, redirects the balance instead of burning and reissuing it, and emits a dedicated Seized event. Both paths remain live.To check current exposure, read the policy IDs assigned to SEIZE_HOLDER_POLICY and TRANSFER_SENDER_POLICY using token.policyId(...) (IB20.policyId, src/interfaces/IB20.sol), then query the Policy Registry’s isAuthorized(policyId, account) with the pooling contract’s own address against each ID. If the contract is not authorized under the seize-holder policy, or is blocked under the transfer-sender policy, funds can be seized or burned from that vault balance under the current configuration. This check is point-in-time only — an issuer can later change either slot with updatePolicy, so “not seizable today” is not a durable guarantee.