Audience: teams integrated against the base B20 surface on Beryl (live today) that perform administrative balance removal, today via the deprecatedburnBlocked. This note covers only the seize surface landing at the Cobalt hardfork and what it means forburnBlocked. The surface is shared, so it applies to both B20 Asset and B20 Stablecoin.
Summary
At Cobalt, the base B20 surface gains a first-class seize operation.seizeWithMemo(from, to, amount, memo) reassigns a holder’s balance to a destination in one admin call, gated by a new
SEIZE_ROLE, a new SEIZE pause vector, and two new policy slots (SEIZE_HOLDER_POLICY,
SEIZE_RECEIVER_POLICY).
Nothing you call today breaks: every Beryl selector, event topic, and error keeps its exact 4-byte
selector or topic0 and stays dialable at Cobalt. In particular, burnBlocked is deprecated but
unchanged (same selector, same events, same behavior) and remains callable.
To migrate, move administrative balance removal from burnBlocked to seizeWithMemo: seize to a
treasury or self address, then call burn if you want the supply destroyed.
Seize is opt-in per token. The surface exists at Cobalt, but seize does nothing until the issuer
configures SEIZE_HOLDER_POLICY. With the slot unset (always-allow), no account is seizable, and
every seizeWithMemo call reverts AccountNotSeizable. An issuer that never sets the policy has, in
effect, no seize capability on that token.
Cobalt hasn’t gone live yet. Until it activates, only the Beryl surface exists on-chain, and every
seize*/SEIZE_* selector below is undialable.
Mapping table
The selectors and topic0s below are the real values from the frozen ABIs:crates/common/precompiles/src/common/abi/v1.rs for Beryl,
crates/common/precompiles/src/common/abi/v2.rs for Cobalt. Every Beryl symbol keeps its selector
at Cobalt. Seize lives on the shared IB20 surface, so it’s identical across Asset and Stablecoin.
Functions
Events
Errors
Pause features
PausableFeature is append-only. Cobalt adds one ordinal.
seizeWithMemo is gated by the new SEIZE vector, not BURN. burnBlocked stays under BURN.
New at Cobalt: adopt these
seizeWithMemo(from, to, amount, memo)
This is the canonical administrative balance-removal path. It’s a transfer: the balance moves from
from to to, and totalSupply is unchanged. It runs as an admin operation that skips allowance
and the transfer policies (TRANSFER_SENDER/RECEIVER/EXECUTOR_POLICY). It emits, in order:
Transfer(from, to, amount)Memo(caller, memo)(a memo ofbytes32(0)is allowed)Seized(caller, from, to, amount)
- Role: the caller must hold
SEIZE_ROLE, or the call revertsAccessControlUnauthorizedAccount. - Pause:
SEIZEmust not be paused, or the call revertsContractPaused(SEIZE). - Addresses:
to != address(0)andfrom != to, or the call revertsInvalidReceiver.from != address(0), or the call revertsInvalidSender. - Holder gate:
frommust be blocked underSEIZE_HOLDER_POLICY, that is, not authorized by it, or the call revertsAccountNotSeizable. An unset slot reads as always-allow, so no account is seizable until an issuer configuresSEIZE_HOLDER_POLICY. - Destination gate:
tomust be authorized underSEIZE_RECEIVER_POLICY, which mirrorsMINT_RECEIVER_POLICYand is always enforced. But an unset slot is always-allow, so a token can seize to any destination (a treasury doesn’t need to be allowlisted) until the slot is set. - Balance:
from’s balance must be>= amount, or the call revertsInsufficientBalance.
AccountNotSeizable fires before PolicyForbids(SEIZE_RECEIVER_POLICY, ...), which fires before InsufficientBalance.
burnBlocked is deprecated, but unchanged and still dialable
burnBlocked(from, amount) keeps working exactly as it does on Beryl:
- It destroys
amountfrom afromblocked underTRANSFER_SENDER_POLICY, without spending an allowance. It emitsTransfer(from, address(0), amount)andBurnedBlocked(caller, from, amount)(noMemo). - It’s gated by
BURN_BLOCKED_ROLEand theBURNpause vector. - It reverts
AccountNotBlockedwhenfromis authorized underTRANSFER_SENDER_POLICY.
burnBlocked(from, amount) with seizeWithMemo(from, treasury, amount, memo),
then call burn(amount) from the treasury if you still want the supply destroyed. This crosses two
policy, role, and pause domains (see the edge cases below), so it isn’t a drop-in selector swap.
Guarantees and edge cases
Q: Does seize changetotalSupply? Is it a burn?
No. Seize is a transfer: it reassigns amount from from to to and leaves totalSupply
untouched. burnBlocked is the burn: it sends to address(0) and reduces supply. To reproduce the
old burn-blocked outcome, seize to a treasury or self address, then call burn.
Q: seizeWithMemo and burnBlocked both target “bad” accounts. Do they read the same set?
No, and this is deliberate. seizeWithMemo reads SEIZE_HOLDER_POLICY. burnBlocked reads
TRANSFER_SENDER_POLICY. A token can define a seizable set that’s distinct from its
transfer-blocked set. In both cases, “eligible” means not authorized by the relevant policy, and an
unset policy (always-allow) means nobody is eligible.
Q: Can I pause seize without pausing burns, or vice versa?
Yes. SEIZE (ordinal 3) and BURN (ordinal 2) are independent pause bits. Pausing BURN doesn’t
stop seizeWithMemo, and pausing SEIZE doesn’t stop burn, burnWithMemo, or burnBlocked.
Q: Do SEIZE_ROLE and BURN_BLOCKED_ROLE overlap?
No. seizeWithMemo requires SEIZE_ROLE. burnBlocked requires BURN_BLOCKED_ROLE. Granting one
doesn’t grant the other.
Q: I never configured the seize policies. What happens if I call seizeWithMemo?
It reverts AccountNotSeizable(from) for every from, because an unset SEIZE_HOLDER_POLICY is
always-allow, so no account is seizable. You must configure SEIZE_HOLDER_POLICY to designate
seizable holders before seize does anything. (Leaving SEIZE_RECEIVER_POLICY unset simply permits
any destination.)
Q: Does seize consult the transfer policies or spend an allowance?
No. It’s an admin operation: it bypasses TRANSFER_SENDER/RECEIVER/EXECUTOR_POLICY and allowances,
and enforces only SEIZE_HOLDER_POLICY (on from) and SEIZE_RECEIVER_POLICY (on to).
Q: Is seize available on B20 Stablecoin as well as B20 Asset?
Yes. It’s defined on the shared IB20 surface, so both variants expose the identical
seizeWithMemo selector, Seized topic0, AccountNotSeizable selector, SEIZE_* getters, and
SEIZE pause bit at Cobalt.