Skip to main content
At the Cobalt hardfork, the B20 Asset multiplier surface becomes conformant with ERC-8056 (“Scaled UI Amount”) and gains a scheduled setter, updateUIMultiplier, for corporate actions such as stock splits and reinvested dividends. Nothing you call on Beryl breaks. Every Beryl selector, event topic0, and error keeps its exact bytes and stays dialable at Cobalt.
Cobalt is not yet live. Every Cobalt-only selector, event, and error listed on this page is undialable until the hardfork activates. Only the Beryl surface currently exists on-chain.

Audience and compatibility promise

This page is for teams already integrated against the Beryl B20 Asset multiplier surface (multiplier, scaledBalanceOf, toScaledBalance, toRawBalance, updateMultiplier, MultiplierUpdated). The migration is additive:
  • Every Beryl symbol keeps its exact 4-byte selector or topic0 at Cobalt and remains dialable. The deprecated-* labels below are advisory.
  • Adopt the canonical ERC-8056 names (uiMultiplier, toUIAmount/fromUIAmount, balanceOfUI, totalSupplyUI) at your own pace.
  • Move routine multiplier changes from the instant updateMultiplier(uint256) to the scheduled updateUIMultiplier(uint256,uint256). Keep the instant setter for emergency overrides only.
Raw balances, transfer semantics, and the Transfer event are unchanged. The multiplier is cosmetic — it rescales only the *UI / scaled views.

Symbol mapping

Selectors and topic0s are the canonical values from the ERC-8056 interfaces the B20 Asset precompile implements at Cobalt. Every Beryl symbol keeps the selector or topic0 it has today.

Functions

OPERATOR_ROLE(), WAD_PRECISION(), announce, isAnnouncementIdUsed, batchMint, extraMetadata, and updateExtraMetadata are carried over unchanged.

Events

Errors

New at Cobalt (adopt these)

Scheduled-update lifecycle

updateUIMultiplier(newMultiplier, effectiveAt) is the canonical corporate-action path. It requires OPERATOR_ROLE. Only one pending update is live at a time.
  1. Schedule. Call updateUIMultiplier(newMultiplier, effectiveAt). effectiveAt must be strictly in the future and must fit uint64.
  2. Pending views. While a pending update is live, newUIMultiplier() returns the scheduled target, effectiveAt() returns the flip timestamp, and uiMultiplier() / multiplier() still return the current value.
  3. Matures lazily. Once block.timestamp >= effectiveAt, uiMultiplier() and multiplier() flip on the next read. No event fires at maturation.
  4. Cancel. cancelUIMultiplierUpdate() clears a live pending and emits UIMultiplierUpdateCancelled(cancelledMultiplier, cancelledEffectiveAt).
To reorder overlapping actions, cancel and reschedule atomically inside a single announcement:

ERC-8056 view aliases

Feature-detect the new surface with ERC-165:

Multiplier ceiling

MAX_UI_MULTIPLIER() returns type(uint128).max. This is the ceiling both setters enforce — the overflow guard that keeps balance * multiplier inside uint256. Read it instead of hard-coding the constant, and validate off-chain to avoid the InvalidMultiplier() revert.

Instant updateMultiplier retained as emergency failsafe

updateMultiplier(uint256) still exists at Cobalt and sets the multiplier immediately. If a live pending update exists, the instant setter clears it. It is kept for emergency overrides only, not routine use — pair it with pause in most cases. When it fires, it emits both the legacy MultiplierUpdated(uint256) and the ERC-8056 UIMultiplierUpdated(old, new, block.timestamp), so a single subscription to UIMultiplierUpdated covers every multiplier change.

Edge cases and guarantees

How do external consumers detect a cancellation? cancelUIMultiplierUpdate() emits UIMultiplierUpdateCancelled(cancelledMultiplier, cancelledEffectiveAt). The instant updateMultiplier(uint256) emits the same event when it supersedes a live pending. Watch that topic to retract a pending flip you previously staged from UIMultiplierUpdated. How do off-chain indexers keep a linear, gap-free lifecycle when the emergency failsafe fires? Every multiplier change — scheduled or emergency — appears on the UIMultiplierUpdated stream. The instant setter emits both MultiplierUpdated(uint256) and UIMultiplierUpdated(old, new, block.timestamp), and emits UIMultiplierUpdateCancelled first if it clears a live pending. Follow the single UIMultiplierUpdated topic and you never miss a change. The legacy MultiplierUpdated topic remains available for indexers that have not migrated. How do I tell a live pending update apart from one that has already matured? A pending is live if effectiveAt() > block.timestamp. While live, newUIMultiplier() returns the scheduled target and differs from uiMultiplier(). After maturation, uiMultiplier() already reflects the new value and newUIMultiplier() == uiMultiplier(). effectiveAt() stays at the (now past) flip timestamp until the next schedule, instant, or cancel overwrites it. A non-zero effectiveAt() that is <= block.timestamp means “already applied,” not “pending.” When no update has ever been scheduled, effectiveAt() == 0. What if I schedule while one is already pending? Reverts UIMultiplierUpdateExists(effectiveAt), but only a live pending blocks. A matured (stale) pending is silently folded into the current multiplier and overwritten. To replace a live schedule, call cancelUIMultiplierUpdate() and then updateUIMultiplier(...) atomically via announce. What are the effectiveAt bounds? effectiveAt must be strictly in the future. effectiveAt <= block.timestamp reverts EffectiveAtInPast(effectiveAt). It must also fit the on-chain field: effectiveAt > type(uint64).max reverts EffectiveAtTooFar(effectiveAt). What are the multiplier bounds? 0 < newMultiplier <= MAX_UI_MULTIPLIER() (type(uint128).max). Zero or above reverts InvalidMultiplier(). This applies to both updateUIMultiplier and updateMultiplier. Read the ceiling from MAX_UI_MULTIPLIER() without risking the revert. Do raw balances or Transfer semantics change? No. The multiplier is cosmetic and rescales only the *UI / scaled views. balanceOf, transfer, totalSupply, and Transfer stay raw and are mechanically unaffected by any multiplier change, scheduled or instant.