For the complete documentation index, see llms.txt. This page is also available as Markdown.

13. Freeze and unfreeze an address or a partial balance

A freeze immobilizes tokens without taking them away. An agent freezes an address entirely, so that it neither sends nor receives, or freezes an amount on an address, so that this amount cannot be sent. Issuers use freezes during an investigation, to enforce a lock-up or a pledge, to keep an address out of the asset, or to protect a holder whose wallet is compromised. The tokens stay owned by their holder throughout.

At a glance

Triggered by

Token Agent

Actors

Issuer, Token Agent, Investor, Platform

Contracts

Token

Preconditions

The caller holds AGENT_ADDRESS_FREEZER to freeze an address, or AGENT_PARTIAL_FREEZER to freeze an amount, in the asset's domain (see 25); the freeze is decided under the Issuer's process

Result

The address, or an amount on it, is immobilized, or released

Actors and roles

Actor
Role in this flow
What they hold

Issuer

Decides the freeze and its release. Does not transact in this flow.

The roles it delegated to its agents

Token Agent

Submits the freeze or the release.

AGENT_ADDRESS_FREEZER, AGENT_PARTIAL_FREEZER, or both

Investor

Holds the tokens that are frozen. Does not transact.

A balance at the address

Platform

Runs the reads and submits with the Token Agent's key.

The Token Agent's signing key, or a key authorized for it

Sequence

Step by step

Step 1. Choose the freeze and read the state

Who: Platform, on behalf of the Token Agent

Call: Off-chain, using read calls

Before the call: The Issuer has decided what to immobilize and why.

What happens: The Platform reads Token.isFrozen(address), Token.getFrozenTokens(address) and Token.balanceOf(address), and chooses the kind of freeze.

Freezing the address
Freezing an amount

Blocks

Every transfer from the address and every transfer to it

Sending that amount. The rest of the balance moves, and the address still receives

Suits

An investigation, a sanctioned or compromised address, keeping an address out of the asset

A lock-up, a pledge, a disputed part of a position

Role

AGENT_ADDRESS_FREEZER

AGENT_PARTIAL_FREEZER

A freeze applies to one address. An investor that holds at its ONCHAINID and in linked wallets is frozen address by address. The Platform lists the investor's addresses with IdentityFactory.getAccounts(identity, start, end) and reads the balance of each.

What changes: Nothing on-chain.

Step 2. Freeze or release an address

Who: Token Agent (AGENT_ADDRESS_FREEZER)

Call: Token.setAddressFrozen(userAddress, freeze), with freeze true to freeze and false to release

Before the call: Step 1 is done.

What the contract checks:

  1. The caller holds AGENT_ADDRESS_FREEZER in the asset's domain. Otherwise the call reverts with AccessManagedUnauthorized.

What changes: The token records the address as frozen, or as not frozen, and emits AddressFrozen(userAddress, freeze, agent). While frozen, every transfer, transferFrom and identityTransfer from or to the address reverts with FrozenWallet.

Step 3. Freeze an amount

Who: Token Agent (AGENT_PARTIAL_FREEZER)

Call: Token.freezePartialTokens(userAddress, amount)

Before the call: Step 1 is done.

What the contract checks:

  1. The caller holds AGENT_PARTIAL_FREEZER in the asset's domain. Otherwise AccessManagedUnauthorized.

  2. The amount already frozen plus amount does not exceed the balance of the address. Otherwise ERC20InsufficientBalance.

What changes: The frozen amount of the address increases by amount. The token emits TokensFrozen(userAddress, amount). The free balance, which is what the holder can send, decreases by the same amount. balanceOf does not change.

Step 4. Release an amount

Who: Token Agent (AGENT_PARTIAL_FREEZER)

Call: Token.unfreezePartialTokens(userAddress, amount)

Before the call: The Issuer has decided to release all or part of what is frozen.

What the contract checks:

  1. The caller holds AGENT_PARTIAL_FREEZER. Otherwise AccessManagedUnauthorized.

  2. amount does not exceed the frozen amount of the address. Otherwise AmountAboveFrozenTokens.

What changes: The frozen amount decreases by amount. The token emits TokensUnfrozen(userAddress, amount). The free balance increases by the same amount.

State after the flow

  • Token.isFrozen(address) reflects the address freeze.

  • Token.getFrozenTokens(address) reflects the frozen amount, which is never above Token.balanceOf(address).

  • Token.freeBalanceOf(address) is the balance less the frozen amount.

  • Token.balanceOf(address), Token.totalSupply() and ModularCompliance.positionOf(identity) are unchanged. Frozen tokens are still owned and still counted by every rule.

  • The eligibility of the holder is unchanged.

Failure modes

Symptom
Cause
Fix

Reverts with AccessManagedUnauthorized

The caller does not hold the role of this kind of freeze. The two are separate roles (check 1 of steps 2 to 4)

Grant the role: 25

freezePartialTokens reverts with ERC20InsufficientBalance

The total frozen amount would exceed the balance of the address (step 3, check 2)

Freeze at most the balance less what is already frozen

unfreezePartialTokens reverts with AmountAboveFrozenTokens

More is released than is frozen (step 4, check 2)

Read getFrozenTokens and release at most that

The address is frozen and its tokens still moved

An agent burned them, forced a transfer or ran a recovery. These operations go through a freeze

Intended. A freeze stops holders and spenders, not the agents

The address is frozen and it received tokens

An agent minted to it, or forced a transfer to it. A freeze blocks transfers by holders

Intended

The investor is frozen and still moved tokens

Another of its addresses is not frozen

Freeze each address that holds tokens

The investor's tokens on a satellite still move

A freeze applies to the reference chain

Recall the position: 32. A recalled position arrives at the holder's address on the reference chain even when that address is frozen

A frozen amount disappeared

A burn or a forced transfer needed it and released it, with a TokensUnfrozen event

Read the events of the address

Common misunderstandings

Wrong: "Frozen tokens are seized."

Right: A freeze changes what can be done with tokens, not whose they are. They remain in balanceOf, in the position of the identity and in every rule about distribution. Taking them is a forced transfer or a burn, each with its own role.

Wrong: "Freezing an investor freezes everything it owns."

Right: A freeze is on an address. An investor's ONCHAINID and each of its linked wallets are separate addresses, and positions on satellites are not reached at all. Freezing an investor means freezing each of its addresses on the reference chain, and recalling what is on satellites.

Wrong: "A frozen address has to be unfrozen before an agent can act on it."

Right: A burn, a forced transfer and a recovery go through a freeze and release exactly the frozen amount they need. A recovery carries the frozen amount and the address freeze over to the new address.

Wrong: "A freeze makes the investor ineligible."

Right: Eligibility is about the identity's claims. A freeze is about an address on one asset. A frozen investor stays eligible, and keeps acting freely on its other assets.

Integration notes

  • Show holders their free balance, freeBalanceOf, as what they can send. A transfer above it reverts with ERC20InsufficientBalance even though balanceOf is higher.

  • Use the batch variants for many addresses: batchSetAddressFrozen, batchFreezePartialTokens, batchUnfreezePartialTokens. Each is gated by the role of its single operation and reverts as a whole if one entry fails.

  • Index AddressFrozen, TokensFrozen and TokensUnfrozen. AddressFrozen names the agent that acted. During a recovery it names the token itself.

  • A freeze is the way to keep an address out of an asset. Removing its registration does not do it.

  • Freezes are per asset. Freezing an address on one token has no effect on another.

  • Record the reason and the authority for each freeze off-chain. The chain records who froze what and when, not why.

Last updated