IB20 interface, so both the Asset and Stablecoin variants expose the identical selectors, events, and errors.
Seize replaces burnBlocked as the recommended path for regulated issuers. burnBlocked remains dialable and unchanged for backwards compatibility, but new integrations should adopt seizeWithMemo.
When to use seize
Reach forseizeWithMemo when you need to move a holder’s balance under administrative authority — for example, to comply with a court order, recover funds from a compromised account, or move balances off a sanctioned address. Seize is a transfer, not a burn: totalSupply is unchanged and the balance moves from the holder to a destination you specify (typically a treasury or self address).
If you want to destroy the seized supply, seize to a treasury first and then call burn from the treasury. This two-step flow reproduces the outcome of the old burnBlocked path with clearer accounting.
seizeWithMemo
seizeWithMemo reassigns amount from from to to as an admin operation. It skips allowance checks and the transfer policies (TRANSFER_SENDER_POLICY, TRANSFER_RECEIVER_POLICY, TRANSFER_EXECUTOR_POLICY), and enforces only the new seize policies described below.
The call emits three events, in order:
Transfer(from, to, amount)Memo(caller, memo)— a memo ofbytes32(0)is permittedSeized(caller, from, to, amount)
Requirements
Guards are evaluated in the order above. When several would fail, the earlier revert wins:
AccountNotSeizable before PolicyForbids(SEIZE_RECEIVER_POLICY, ...) before InsufficientBalance.
Roles and pause
Cobalt introduces one role and one pause feature dedicated to seize. Neither overlaps with the existing burn surface.PausableFeature is append-only. Cobalt appends SEIZE after the existing Beryl ordinals (TRANSFER=0, MINT=1, BURN=2), and ALL_FEATURES_PAUSED becomes 15 (0b1111).
Because SEIZE and BURN are independent bits, pausing BURN does not stop seizeWithMemo, and pausing SEIZE does not stop burn, burnWithMemo, or burnBlocked.
Policies
Seize adds two new policy scopes. Both point into the shared PolicyRegistry via auint64 policy ID and are written through updatePolicy(scope, policyId).
SEIZE_HOLDER_POLICY inverts the usual sense: a holder must be denied by the policy to be eligible for seize. This lets an issuer maintain a BLOCKLIST of seizable accounts distinct from its transfer-blocked set.
Seize is opt-in per token
Every policy scope defaults toALWAYS_ALLOW at token creation. Because SEIZE_HOLDER_POLICY defaults to always-allow, no account is seizable until an issuer configures the scope. Every seizeWithMemo call reverts AccountNotSeizable(from) on a token that has not set SEIZE_HOLDER_POLICY.
Issuers that never configure SEIZE_HOLDER_POLICY have, in effect, no seize capability on that token.
SEIZE_RECEIVER_POLICY left unset mirrors MINT_RECEIVER_POLICY: it permits any destination. A treasury does not need to be allowlisted until an issuer opts into restricting seize destinations.
Events and errors
burnBlocked is deprecated
burnBlocked(from, amount) keeps its Beryl behavior at Cobalt — same selector, same events, same guards. It remains callable indefinitely for backwards compatibility, but new integrations should use seizeWithMemo.
For reference, burnBlocked still:
- Destroys
amountfrom afromthat is denied byTRANSFER_SENDER_POLICY, without spending an allowance. - Emits
Transfer(from, address(0), amount)andBurnedBlocked(caller, from, amount). It does not emitMemo. - Is gated by
BURN_BLOCKED_ROLEand theBURNpause vector. - Reverts
AccountNotBlocked(from)whenfromis authorized underTRANSFER_SENDER_POLICY.
Migrating off burnBlocked
Replace a burnBlocked(from, amount) call with a seize to a treasury, followed by a burn from that treasury if you want the supply destroyed:
- The seize step needs
SEIZE_ROLE, an unpausedSEIZEvector,fromdenied bySEIZE_HOLDER_POLICY, andtreasuryauthorized bySEIZE_RECEIVER_POLICY. - The burn step needs
BURN_ROLEon the treasury and an unpausedBURNvector.
SEIZE_HOLDER_POLICY to designate seizable accounts, and update your admin runbooks before switching over.