diff --git a/docs/base-chain/specs/upgrades/beryl/b20.mdx b/docs/base-chain/specs/upgrades/beryl/b20.mdx
index 2daaf3aa5..19effd55c 100644
--- a/docs/base-chain/specs/upgrades/beryl/b20.mdx
+++ b/docs/base-chain/specs/upgrades/beryl/b20.mdx
@@ -143,7 +143,11 @@ New supply is created via `mint` / `mintWithMemo`, gated by `MINT_ROLE`. The rec
Two burn paths exist:
- **`burn` / `burnWithMemo`** - caller burns from their own balance. Gated by `BURN_ROLE`.
-- **`burnBlocked`** - burns from a third party's balance. Gated by `BURN_BLOCKED_ROLE`. The target account MUST be denied by `TRANSFER_SENDER_POLICY` - this is the freeze-and-seize path for regulated issuers.
+- **`burnBlocked`** - burns from a third party's balance. Gated by `BURN_BLOCKED_ROLE`. The target account MUST be denied by `TRANSFER_SENDER_POLICY`.
+
+
+`burnBlocked` is deprecated as of the [Cobalt hardfork](/base-chain/specs/upgrades/cobalt/b20-seize-surface). It remains dialable and unchanged, but new integrations should use `seizeWithMemo` — the first-class administrative balance-removal path — and burn from a treasury if supply destruction is still required.
+
## Supply Cap
diff --git a/docs/base-chain/specs/upgrades/cobalt/b20-seize-surface.mdx b/docs/base-chain/specs/upgrades/cobalt/b20-seize-surface.mdx
new file mode 100644
index 000000000..2b3d01b50
--- /dev/null
+++ b/docs/base-chain/specs/upgrades/cobalt/b20-seize-surface.mdx
@@ -0,0 +1,155 @@
+---
+title: "B20 seize surface"
+description: "Learn how the Cobalt hardfork adds a first-class seize operation to B20 Asset and Stablecoin, and how it supersedes burnBlocked for administrative balance removal."
+---
+
+Cobalt adds a first-class **seize** operation to the shared B20 interface. Issuers can reassign a holder's balance to a destination in one admin call, gated by dedicated role, pause, and policy slots. The surface is defined on the shared `IB20` interface, so both the [Asset and Stablecoin variants](/base-chain/specs/upgrades/beryl/b20#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`.
+
+
+Cobalt is not live yet. Every seize-related selector, event topic, and error described on this page is undialable until the Cobalt hardfork activates. Only the Beryl surface is available on-chain today.
+
+
+## When to use seize
+
+Reach for `seizeWithMemo` 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`
+
+```solidity
+function seizeWithMemo(
+ address from,
+ address to,
+ uint256 amount,
+ bytes32 memo
+) external;
+```
+
+`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:
+
+1. `Transfer(from, to, amount)`
+2. `Memo(caller, memo)` — a memo of `bytes32(0)` is permitted
+3. `Seized(caller, from, to, amount)`
+
+### Requirements
+
+| Guard | Requirement | Reverts with |
+|-------|-------------|--------------|
+| Role | Caller holds `SEIZE_ROLE` | `AccessControlUnauthorizedAccount` |
+| Pause | `SEIZE` feature is not paused | `ContractPaused(SEIZE)` |
+| Sender address | `from != address(0)` | `InvalidSender` |
+| Receiver address | `to != address(0)` and `from != to` | `InvalidReceiver` |
+| Holder gate | `from` is denied by `SEIZE_HOLDER_POLICY` | `AccountNotSeizable(from)` |
+| Destination gate | `to` is authorized by `SEIZE_RECEIVER_POLICY` | `PolicyForbids(SEIZE_RECEIVER_POLICY, to)` |
+| Balance | `balanceOf(from) >= amount` | `InsufficientBalance` |
+
+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.
+
+| Constant | Purpose |
+|----------|---------|
+| `SEIZE_ROLE` | Required to call `seizeWithMemo`. Value `keccak256("SEIZE_ROLE")`. |
+| `PausableFeature.SEIZE` | Ordinal `3`, storage bit `1 << 3 = 8`. Independent pause vector for `seizeWithMemo`. |
+
+`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](/base-chain/specs/upgrades/beryl/b20#policy-integration). Both point into the shared PolicyRegistry via a `uint64` policy ID and are written through `updatePolicy(scope, policyId)`.
+
+| Scope | Gates | Semantics |
+|-------|-------|-----------|
+| `SEIZE_HOLDER_POLICY` | The `from` of `seizeWithMemo` | A holder is seizable only when it is **denied** by this policy. |
+| `SEIZE_RECEIVER_POLICY` | The `to` of `seizeWithMemo` | A destination is permitted only when it is **authorized** by this policy. |
+
+`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 to `ALWAYS_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.
+
+
+`seizeWithMemo` and `burnBlocked` read different policies. `seizeWithMemo` reads `SEIZE_HOLDER_POLICY`; `burnBlocked` reads `TRANSFER_SENDER_POLICY`. A "transfer-blocked" account is not automatically "seizable" — an issuer must configure `SEIZE_HOLDER_POLICY` explicitly.
+
+
+## Events and errors
+
+| Symbol | Type | Purpose |
+|--------|------|---------|
+| `Seized(address caller, address from, address to, uint256 amount)` | event | Emitted by `seizeWithMemo` after `Transfer` and `Memo`. |
+| `AccountNotSeizable(address account)` | error | Thrown by `seizeWithMemo` when `from` is authorized under `SEIZE_HOLDER_POLICY`. |
+
+## `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 `amount` from a `from` that is **denied by `TRANSFER_SENDER_POLICY`**, without spending an allowance.
+- Emits `Transfer(from, address(0), amount)` and `BurnedBlocked(caller, from, amount)`. It does not emit `Memo`.
+- Is gated by `BURN_BLOCKED_ROLE` and the `BURN` pause vector.
+- Reverts `AccountNotBlocked(from)` when `from` is authorized under `TRANSFER_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:
+
+```solidity
+// Old: single admin call that destroys supply directly
+b20.burnBlocked(from, amount);
+
+// New at Cobalt: seize to a treasury, then optionally burn the supply
+b20.seizeWithMemo(from, treasury, amount, memo);
+// Called from the treasury address, gated by BURN_ROLE:
+b20.burn(amount);
+```
+
+The migration crosses two role, pause, and policy domains:
+
+- The seize step needs `SEIZE_ROLE`, an unpaused `SEIZE` vector, `from` denied by `SEIZE_HOLDER_POLICY`, and `treasury` authorized by `SEIZE_RECEIVER_POLICY`.
+- The burn step needs `BURN_ROLE` on the treasury and an unpaused `BURN` vector.
+
+It is not a drop-in selector swap. Grant the new role, configure `SEIZE_HOLDER_POLICY` to designate seizable accounts, and update your admin runbooks before switching over.
+
+## Selectors
+
+Selectors and topic0 values below match the frozen Cobalt ABI. Beryl symbols keep their exact 4-byte selectors and topic0 values at Cobalt.
+
+### Functions
+
+| Symbol | Selector | Status |
+|--------|----------|--------|
+| `seizeWithMemo(address,address,uint256,bytes32)` | `0xf916d81b` | new at Cobalt |
+| `SEIZE_ROLE()` | `0x3c7e9ba5` | new at Cobalt |
+| `SEIZE_HOLDER_POLICY()` | `0xb279d311` | new at Cobalt |
+| `SEIZE_RECEIVER_POLICY()` | `0xb31da27f` | new at Cobalt |
+| `burnBlocked(address,uint256)` | `0xec0cf3dc` | deprecated, still dialable |
+| `BURN_BLOCKED_ROLE()` | `0x32ad9be8` | unchanged |
+
+### Events
+
+| Symbol | Topic0 | Status |
+|--------|--------|--------|
+| `Seized(address,address,address,uint256)` | `0xa9aec5d8b86e2fa2fd6ac3af62f2622e3dfdab1967d4cbbb56a5df7d74cb887c` | new at Cobalt |
+| `BurnedBlocked(address,address,uint256)` | `0x0b552e96653fd6842da37c477005d3b5c08a8c7d3631b1f43787b2dc9a1006a3` | unchanged, still emitted by `burnBlocked` |
+
+### Errors
+
+| Symbol | Selector | Status |
+|--------|----------|--------|
+| `AccountNotSeizable(address)` | `0x91dbbc8d` | new at Cobalt |
+| `AccountNotBlocked(address)` | `0x64a5cb46` | unchanged, still thrown by `burnBlocked` |
diff --git a/docs/docs.json b/docs/docs.json
index f6ddd67f2..b231260fa 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -262,7 +262,8 @@
{
"group": "Cobalt",
"pages": [
- "base-chain/specs/upgrades/cobalt/eip-8130"
+ "base-chain/specs/upgrades/cobalt/eip-8130",
+ "base-chain/specs/upgrades/cobalt/b20-seize-surface"
]
},
{