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

23. Revoke a wallet

Revoking a wallet ends its binding to an identity. The Identity Owner does it when a wallet is compromised, lost or retired. From that moment the wallet can no longer receive any asset. What it holds stays attributed to the identity, so nothing is lost. A revocation is terminal: the wallet can never be linked again, to this identity or to any other.

At a glance

Triggered by

Identity Owner

Actors

Identity Owner, Investor, Token Agent, Platform

Contracts

IdentityFactory, ONCHAINID, Token (for the tokens left in the wallet)

Preconditions

The wallet is linked to the identity with the status Active (see 07); the owner controls a MANAGEMENT key of the identity; the identity is not of a single-binding type

Result

The wallet has the status Revoked: it is refused as a recipient by every asset, and it still resolves to the identity for attribution

Actors and roles

Actor
Role in this flow
What they hold

Identity Owner

Revokes the wallet and moves its tokens to an address it controls.

A MANAGEMENT key of the identity to revoke, an ACTION key to move tokens

Investor

The Identity Owner in its capacity as holder. Keeps its whole position.

The identity's claims and its other addresses, unchanged

Token Agent

Recovers the wallet when the owner asks, or when part of the balance is frozen. Freezes the address if needed.

AGENT_RECOVERY_ADDRESS or AGENT_ADDRESS_FREEZER on the asset

Platform

Runs the reads, relays the calls and verifies the result.

Whatever keys the owner has authorized

Sequence

Step by step

Step 1. Check the wallet and what it holds

Who: Platform, on behalf of the Identity Owner

Call: Off-chain, using read calls

Before the call: The owner has decided to retire the wallet.

What happens: The Platform reads IdentityFactory.getAccountStatus(account), which must be Active, and IdentityFactory.getIdentity(account), which must return the owner's identity. For each asset the owner holds, it reads Token.balanceOf(wallet), to know what will have to be moved, and it identifies a destination: the identity itself, or another active wallet of the identity. For a compromised wallet, the Platform prepares step 2 and step 3 to be sent without delay.

What changes: Nothing on-chain.

Step 2. Revoke the wallet

Who: Identity Owner (MANAGEMENT key), through the identity

Call: IdentityFactory.revokeAccount(account), called by the identity

Before the call: Step 1 is done. A MANAGEMENT key instructs the identity. An ACTION key cannot: the factory is a management-grade target.

What the contract checks:

  1. The caller is an identity of a type that can link and revoke. Otherwise CannotRevokeFromNonSigningIdentity.

  2. account is a well-formed interoperable address in canonical form. Otherwise the call reverts on the encoding, or with NonCanonicalAccount.

  3. The wallet is linked to the calling identity. Otherwise WalletNotLinkedToIdentity.

  4. The wallet is Active. Otherwise WalletNotActive.

What changes: The factory sets the wallet's status to Revoked and removes it from the identity's list of active wallets. The record of the binding stays. The factory emits AccountRevoked(account, identity, caller). From the next eligibility check, every asset refuses the wallet as a recipient.

Step 3. Move the tokens out of the wallet

Only when the wallet holds tokens on the reference chain.

Who: Identity Owner (ACTION key), through the identity

Call: Token.identityTransfer(wallet, destination, amount), called by the identity, once per asset

Before the call: destination is an address that is verified for the asset: the identity itself, or another active wallet of the identity.

What the contract checks:

  1. The caller is the identity the wallet resolves to. A revoked wallet still resolves to its identity. Otherwise NotLinkedIdentity.

  2. The token is not paused, and neither address is frozen. Otherwise the call reverts.

  3. The amount is within the wallet's free balance. Otherwise the call reverts.

  4. The destination is verified. Otherwise the call reverts.

  5. The compliance allows the movement. A movement between two addresses of one identity changes no position.

What changes: The tokens move from the revoked wallet to the destination. The token emits Transfer(wallet, destination, amount) and IdentityTransfer(identity, wallet, destination, amount). The position of the identity does not change.

The alternative is a recovery by an agent of the asset, with recoveryAddress (15). For free tokens the result is the same. For frozen tokens it is not: identityTransfer moves the free balance only, and a recovery moves the whole balance and freezes the same amount again at the new address. A wallet with a frozen amount is therefore emptied by an agent.

State after the flow

  • IdentityFactory.getAccountStatus(account) returns Revoked.

  • IdentityFactory.getIdentity(account) returns zero. IdentityFactory.getIdentityIncludingRevoked(account) still returns the identity, with the status Revoked.

  • IdentityFactory.getAccounts(identity, start, end) no longer includes the wallet.

  • For every asset, TREXRegistry.isVerified is false for the wallet, and TREXRegistry.identity still returns the identity.

  • The identity's keys, claims, other wallets and eligibility are unchanged.

  • The identity's position in each asset is unchanged. After step 3, it sits at the destination.

  • The wallet can never be linked again.

Failure modes

Symptom
Cause
Fix

Reverts with NonCanonicalAccount, or on the address encoding

The interoperable address is malformed or not canonical (step 2, check 2)

Encode the wallet exactly as it was linked

Reverts with CannotRevokeFromNonSigningIdentity

The identity is of a single-binding type, such as the identity of a token (step 2, check 1)

Not possible for this identity

Reverts with WalletNotLinkedToIdentity

The wallet belongs to another identity, or to none (step 2, check 3)

Read getIdentityIncludingRevoked(account)

Reverts with WalletNotActive

The wallet is already revoked (step 2, check 4)

Nothing to do

The identity's instruction is refused

The key that instructed the identity holds ACTION only

Use a MANAGEMENT key

identityTransfer reverts with NotLinkedIdentity

The caller is not the identity the wallet resolves to (step 3, check 1)

Have the identity make the call

identityTransfer reverts on the destination

The destination is not verified for the asset (step 3, check 4)

Send to the identity itself or to an active wallet of an eligible identity

identityTransfer reverts with ERC20InsufficientBalance, or leaves part of the balance in the wallet

Part of the balance is frozen. A transfer moves free tokens only (step 3, check 3)

An agent recovers the wallet, which carries the frozen amount over: 15

identityTransfer reverts because of a freeze or the pause

The wallet or the destination is frozen, or the token is paused (step 3, check 2)

Ask the asset's agents

The tokens left the wallet before the owner moved them

The wallet's key was compromised and the attacker transferred first. A revoked wallet can still send

Revoke and move the tokens at once. Ask the asset's agents to freeze the address if in doubt

The owner wants the wallet back

A revocation is terminal

Link a new wallet: 07

Common misunderstandings

Wrong: "Revoking a wallet freezes the tokens in it."

Right: A revoked wallet can no longer receive. It can still send. Revoking protects the holder against tokens being sent to an address it no longer trusts. To immobilize what the wallet holds, the holder moves the tokens out through its identity, or the asset's agents freeze the address.

Wrong: "Revoking a wallet loses the tokens in it."

Right: The wallet keeps resolving to its identity for attribution. The position still belongs to the identity. The owner moves the free tokens out with identityTransfer, or an agent recovers the wallet, frozen tokens included.

Wrong: "A revoked wallet can be linked again later."

Right: A revocation is terminal. The record of the binding stays on-chain with the status Revoked, and the wallet is refused by every later attempt to link it, to any identity.

Wrong: "Revoking a wallet revokes the investor."

Right: Eligibility belongs to the identity, not to a wallet. The identity keeps its claims, its keys and its other addresses, and stays eligible.

Integration notes

  • For a compromised wallet, send the revocation and the transfers out as fast as possible, ideally in one user operation signed by a MANAGEMENT key. The attacker holds the wallet's key and can still send.

  • Moving the tokens before revoking is equally valid, and is the safer order when the owner has time.

  • For an asset, attribute a balance with TREXRegistry.identity(address) and test admission with TREXRegistry.isVerified(address). At the factory, getIdentityIncludingRevoked(account) answers for wallet bindings, revoked ones included, and getIdentity(account) for active bindings only. Both return the identity itself for an identity's own address.

  • Index AccountRevoked. An application that lists a holder's addresses removes the wallet from the active list and keeps it for history.

  • Revoking the last wallet of an identity does not disable the identity. Its keys still work, it can hold tokens at its own address, and it can link a new wallet.

  • A wallet on a satellite is revoked with the same call, with its interoperable address. Its bridged position stays recorded for the identity.

Last updated