Skip to main content
Occasionally you need to move a balance out of an account you have blocked: a holder who lost their keys, a court order, or a sanctions hold. seizeWithMemo moves the tokens from the holder to a safekeeping account in one admin call. It is not a burn and not a mint: totalSupply does not change, the memo carries your case reference onchain, and the token emits a dedicated Seized event that auditors and indexers can follow.

Demo

The demo uses a local browser-generated account to submit real transactions on Base Vibenet. If Vibenet or its B20 features are unavailable, it automatically switches to an illustrative offline version.
New to B20? See the B20 Token Standard for the concepts and a full launch walkthrough. These samples target base-std@1505323, viem@2.55.11, and Base Foundry v1.1.1.
seizeWithMemo is part of the Cobalt B20 surface. It is live on Base Vibenet today and reaches Base mainnet with the Cobalt hard fork. Until then, mainnet tokens only have the deprecated burnBlocked path, which destroys the balance and requires a separate mint to reissue it. See the seize changelog entry.

Before You Start

You need all of the following:
  • A B20 Stablecoin you administer, with DEFAULT_ADMIN_ROLE so you can grant roles and attach policies.
  • An account that will call seizeWithMemo (the seizer). Grant it SEIZE_ROLE.
  • The blocklist policy ID you created in Block an Account, with the holder already on it.
  • A non-zero safekeeping destination that is not the holder and not the token’s own address, typically your treasury.
  • SEIZE not paused. pause([SEIZE]) blocks every seize until unpause([SEIZE]). Pausing TRANSFER, MINT, or BURN leaves seize live.
Three controls decide whether a seize can run, checked in this order: SEIZE_EXEMPT_POLICY is inverted relative to the transfer scopes. Attach a blocklist: accounts on the list are unauthorized, so they become seizable, and everyone else stays exempt. Do not attach an allowlist or ALWAYS_BLOCK; an empty allowlist authorizes nobody, which would make every holder seizable. Seize does not read the transfer scopes. Blocking a holder under TRANSFER_SENDER_POLICY freezes their outgoing transfers but does not make them seizable until the same blocklist (or another) is attached to SEIZE_EXEMPT_POLICY.

Seize and Verify the Blocked Balance

Grant SEIZE_ROLE to the seizer first. This is a one-time setup per token:
Grant SEIZE_ROLE
Then attach your blocklist to SEIZE_EXEMPT_POLICY and move the balance:
Attaching the policy emits PolicyUpdated and takes effect on the next seizeWithMemo. You can point more than one token at the same blocklist. The seize itself emits, in order, Transfer(from, to, amount), Memo(caller, memo), and Seized(caller, from, to, amount). Once the balance sits in the treasury it is an ordinary balance. Reissue it to the holder’s new address with a plain transfer, or hold it while the case is open. To lock destinations as well, create an allowlist containing the treasury and attach it to SEIZE_RECEIVER_POLICY. Skip this if any safekeeping address is acceptable; the unset scope already allows every to.
Seized(caller, from, to, amount) appears on the transaction. The blocked balance falls, the treasury balance rises by the same amount, and totalSupply is unchanged.

Common Errors

These errors follow the order seizeWithMemo checks them. A balance already sitting at the token’s own address can still be recovered: seizeWithMemo(address(token), treasury, amount, memo) is allowed. Seizing to the token’s address is not.
Prefer this path over the deprecated burnBlocked workaround, which burns the blocked balance and mints a replacement. That path dips and restores totalSupply, emits no Seized event, and gives reconciliation two unrelated transactions instead of one traceable movement.

See Also

Block an Account

Deny a specific address and make it seizable.

Reconcile with Memos

Match operations using onchain memos.