> ## Documentation Index
> Fetch the complete documentation index at: https://base-a060aa97-docs-use-case-ia-overhaul.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# B20: seize surface and burnBlocked deprecation (Cobalt)

> The B20 seize surface at Cobalt and the deprecation of burnBlocked. Migration notes for teams integrated against the Beryl seize path.

> **Audience:** teams integrated against the base B20 surface on Beryl (live today) that perform
> administrative balance removal, today via the deprecated `burnBlocked`. This note covers only the
> seize surface landing at the Cobalt hardfork and what it means for `burnBlocked`. 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

| Beryl symbol (selector)                     | Cobalt (selector)                                             | Status                 | Why                                                                                                                                                  |
| ------------------------------------------- | ------------------------------------------------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `burnBlocked(address,uint256)` `0xec0cf3dc` | `burnBlocked(address,uint256)` `0xec0cf3dc`                   | deprecated-dialable    | Kept unchanged for backward compatibility. Prefer `seizeWithMemo` then `burn`. Destroys supply and reads `TRANSFER_SENDER_POLICY`.                   |
| `BURN_BLOCKED_ROLE()` `0x32ad9be8`          | `BURN_BLOCKED_ROLE()` `0x32ad9be8`                            | carried over unchanged | Still gates `burnBlocked` only.                                                                                                                      |
| —                                           | `seizeWithMemo(address,address,uint256,bytes32)` `0xf916d81b` | new                    | Admin balance reassignment. A transfer, not a burn.                                                                                                  |
| —                                           | `SEIZE_ROLE()` `0x3c7e9ba5`                                   | new                    | Required to call `seizeWithMemo`. Value `keccak256("SEIZE_ROLE")` = `0x3469b8b0d89e9604f8510ed143f74a8336d22955d4f83e23bf53d9414e27f432`.            |
| —                                           | `SEIZE_HOLDER_POLICY()` `0xb279d311`                          | new                    | Policy slot checked against `from`. Value `keccak256("SEIZE_HOLDER_POLICY")` = `0x1497ab2b67ebb0a75dd9cdd6aec9f0e64620e6b87e911af7a088ac12e58d9ef2`. |
| —                                           | `SEIZE_RECEIVER_POLICY()` `0xb31da27f`                        | new                    | Policy slot checked against `to`. Value `keccak256("SEIZE_RECEIVER_POLICY")` = `0xbf15b19caf5c77422c038bc25f26b8b815c3a14f6d04c6616076b81bcfe07b3d`. |

### Events

| Beryl event (topic0)                                                                                          | Cobalt (topic0)                                                                                                | Status                   | Why                                                                                     |
| ------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------ | --------------------------------------------------------------------------------------- |
| `BurnedBlocked(address,address,uint256)` `0x0b552e96653fd6842da37c477005d3b5c08a8c7d3631b1f43787b2dc9a1006a3` | unchanged                                                                                                      | deprecated-still-emitted | Still emitted by `burnBlocked` alongside `Transfer(from, address(0), amount)`.          |
| —                                                                                                             | `Seized(address,address,address,uint256)` `0xa9aec5d8b86e2fa2fd6ac3af62f2622e3dfdab1967d4cbbb56a5df7d74cb887c` | new                      | Emitted by `seizeWithMemo` after `Transfer(from, to, amount)` and `Memo(caller, memo)`. |

### Errors

| Beryl error (selector)                    | Cobalt (selector)                          | Status                   | Why                                                                                                      |
| ----------------------------------------- | ------------------------------------------ | ------------------------ | -------------------------------------------------------------------------------------------------------- |
| `AccountNotBlocked(address)` `0x64a5cb46` | unchanged                                  | present on Beryl already | Thrown by `burnBlocked` when `from` is authorized under `TRANSFER_SENDER_POLICY` (that is, not blocked). |
| —                                         | `AccountNotSeizable(address)` `0x91dbbc8d` | new                      | Thrown by `seizeWithMemo` when `from` is authorized under `SEIZE_HOLDER_POLICY` (that is, not seizable). |

### Pause features

`PausableFeature` is append-only. Cobalt adds one ordinal.

| Beryl ordinals                   | Cobalt addition | Storage bit  | Why                                                                                          |
| -------------------------------- | --------------- | ------------ | -------------------------------------------------------------------------------------------- |
| `TRANSFER=0`, `MINT=1`, `BURN=2` | `SEIZE=3`       | `1 << 3 = 8` | Independent pause vector for `seizeWithMemo`. `ALL_FEATURES_PAUSED` becomes `15` (`0b1111`). |

`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:

1. `Transfer(from, to, amount)`
2. `Memo(caller, memo)` (a memo of `bytes32(0)` is allowed)
3. `Seized(caller, from, to, amount)`

Requirements and guards:

* **Role**: the caller must hold `SEIZE_ROLE`, or the call reverts `AccessControlUnauthorizedAccount`.
* **Pause**: `SEIZE` must not be paused, or the call reverts `ContractPaused(SEIZE)`.
* **Addresses**: `to != address(0)` and `from != to`, or the call reverts `InvalidReceiver`.
  `from != address(0)`, or the call reverts `InvalidSender`.
* **Holder gate**: `from` must be blocked under `SEIZE_HOLDER_POLICY`, that is, not authorized by
  it, or the call reverts `AccountNotSeizable`. An unset slot reads as always-allow, so no account
  is seizable until an issuer configures `SEIZE_HOLDER_POLICY`.
* **Destination gate**: `to` must be authorized under `SEIZE_RECEIVER_POLICY`, which mirrors
  `MINT_RECEIVER_POLICY` and 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 reverts `InsufficientBalance`.

When multiple guards would fail, they take this precedence: holder gate, then destination gate,
then balance. That is, `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 `amount` from a `from` blocked under `TRANSFER_SENDER_POLICY`, without spending an
  allowance. It emits `Transfer(from, address(0), amount)` and `BurnedBlocked(caller, from, amount)`
  (no `Memo`).
* It's gated by `BURN_BLOCKED_ROLE` and the `BURN` pause vector.
* It reverts `AccountNotBlocked` when `from` is authorized under `TRANSFER_SENDER_POLICY`.

To migrate, replace `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 change `totalSupply`? 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.
