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

09. Burn a position

Burning destroys units of the asset held at an address. It is how a redemption, a buy-back or the cancellation of units issued in error lands on the register: once the Issuer has settled the redemption under its own process, an agent burns the corresponding position. A burn has no recipient, so neither gate applies. It is an agent operation that needs no signature from the holder.

At a glance

Triggered by

Token Agent

Actors

Issuer, Token Agent, Investor, Platform

Contracts

Token, ModularCompliance, compliance modules

Preconditions

The caller holds AGENT_BURNER in the asset's domain (see 25); the address holds at least amount on the reference chain; the redemption is settled, or the cancellation is decided, under the Issuer's process

Result

The balance of the address, the position of its identity and the total supply have each decreased by amount

Actors and roles

Actor
Role in this flow
What they hold

Issuer

Decides that the units are to be destroyed: the redemption is accepted and paid. Does not transact in this flow.

The roles it delegated to its agents

Token Agent

Submits the burn.

AGENT_BURNER on the Token

Investor

Holds the position that is burned. Does not transact.

A balance at one of its addresses

Platform

Runs the pre-checks 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. Confirm what is to be burned

Who: Platform, on behalf of the Token Agent

Call: Off-chain, using read calls

Before the call: The redemption is settled under the Issuer's process, or the cancellation is decided. The Platform knows the address and the number of units.

What happens: The Platform reads Token.balanceOf(address), which must cover the amount, and Token.getFrozenTokens(address), to know whether the burn will release frozen tokens. If part of the investor's position is active on a satellite, that part is not in balanceOf and cannot be burned from here: it is recalled first.

What changes: Nothing on-chain.

Step 2. Burn

Who: Token Agent (AGENT_BURNER)

Call: Token.burn(userAddress, amount)

Before the call: Step 1 passed.

What the contract checks:

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

  2. The amount is within the balance of the address, frozen tokens included. Otherwise ERC20InsufficientBalance.

  3. The amount is not zero. The compliance does not record a movement of zero. Otherwise ZeroValue.

Nothing else is checked. The pause does not block a burn. A freeze on the address does not block it. The eligibility of the holder is not evaluated, and the compliance rules are not asked.

What changes: If the amount exceeds the free balance, the token releases the frozen tokens it needs and emits TokensUnfrozen(address, releasedAmount). It debits the address, decreases totalSupply by amount, and emits Transfer(address, 0x0, amount). It then calls ModularCompliance.destroyed(address, amount). The compliance subtracts amount from the position of the holder's identity and calls afterTransfer on every TRACKER module.

State after the flow

  • Token.balanceOf(address) has decreased by amount.

  • Token.totalSupply() has decreased by amount.

  • Token.getFrozenTokens(address) has decreased by what the burn had to release, and is never above the balance.

  • ModularCompliance.positionOf(identity) has decreased by amount for the holder's identity.

  • One Transfer event with to = 0x0 was emitted. An indexer treats it as a redemption or a cancellation.

  • Room is freed under any cap that counts positions.

Failure modes

Symptom
Cause
Fix

Reverts with AccessManagedUnauthorized

The calling key does not hold AGENT_BURNER in the asset's domain (step 2, check 1)

Grant the role: 25. Holding AGENT_MINTER or AGENT is not enough

Reverts with ERC20InsufficientBalance

The address holds less than amount on the reference chain (step 2, check 2)

Read balanceOf. Burn at most the balance

Reverts with ZeroValue

The amount is zero (step 2, check 3)

Burn a positive amount. In a batch, remove the entries of zero

The investor owns more than balanceOf shows

Part of its position is active on a satellite, or held at another of its addresses

Burn at each address. For a satellite position, recall it first

An unexpected TokensUnfrozen event appears

The burn exceeded the free balance and released frozen tokens

Intended. Read getFrozenTokens before burning to anticipate it

The burn succeeds while the token is paused

Burns are allowed during a pause

Intended. A redemption can be part of the response to an incident

The transaction succeeds and the Platform still shows the balance

The indexer ignores Transfer events whose to is the zero address

Index burns as Transfer to the zero address

Common misunderstandings

Wrong: "The holder burns its own tokens."

Right: There is no burn function for holders. burn is gated by AGENT_BURNER. A redemption is requested from the Issuer under the terms of the asset, and an agent executes it.

Wrong: "A burn needs the holder's approval."

Right: A burn takes no allowance and no signature from the holder. It is one of the servicing operations of the register. That is why it is a separate role, and why every burn is visible as a Transfer to the zero address.

Wrong: "A burn is blocked if the holder is frozen or no longer eligible."

Right: A burn passes no gate. It works on a frozen address, on a wallet whose link was revoked, and for a holder whose claims expired. A redemption can always be carried out.

Wrong: "Burning reduces balanceOf but the compliance keeps counting the position."

Right: The token tells the compliance after every burn. The position of the identity goes down by the same amount, so rules that count positions see the room that was freed.

Integration notes

  • Read balanceOf and getFrozenTokens before submitting. The first tells whether the burn can succeed, the second whether it will release a freeze.

  • For a redemption across many investors, use Token.batchBurn(userAddresses, amounts). It is gated by the same role and reverts as a whole if one entry fails.

  • Index Transfer(address, 0x0, amount) as a burn. There is no separate burn event.

  • Store the link between the redemption record and the transaction hash, as for a mint.

  • A burn acts on one address. An investor that holds at its ONCHAINID and in linked wallets is burned address by address.

  • The payment leg of a redemption is outside this flow. The Issuer settles it under its own process, or with an application built for it.

Last updated