> For the complete documentation index, see [llms.txt](https://docs.t-rex.network/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.t-rex.network/token-and-register/modular-compliance.md).

# Modular compliance

The rules of an asset are enforced by its `ModularCompliance` contract, which the token consults on every movement. Each rule is a module. An Issuer composes the rule set its asset requires by binding the corresponding modules, and the compliance evaluates the composed set at the moment of each movement, against the complete distribution of the asset.

This page describes how modules are typed, called, bound and removed. What the compliance gate decides is walked through in [Eligibility and compliance: the two gates](/the-primary-register/eligibility-and-compliance-the-two-gates.md).

## What the compliance is

One `ModularCompliance` serves one token. It has four jobs.

| Job                                                                                | Described in                                                          |
| ---------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| Answer the token before a movement: is it allowed, and may this spender execute it | This page                                                             |
| Keep each identity's total position                                                | [The compliance ledger](/token-and-register/the-compliance-ledger.md) |
| Call the modules that want to be told after a movement                             | This page                                                             |
| Issue and settle the validations for movements on satellite chains                 | Cross-chain: the distributed register                                 |

## Three types of module

A module declares what it is, and the compliance calls it only for that.

| Type      | Function                  | The module answers                             | Called                                                                             |
| --------- | ------------------------- | ---------------------------------------------- | ---------------------------------------------------------------------------------- |
| `RULE`    | `allowedAmount(ctx)`      | The largest amount it allows for this movement | Before a transfer or a mint. When a cross-chain validation is issued.              |
| `SPENDER` | `moduleCheckSpender(ctx)` | Whether this spender may execute this movement | Before an allowance is spent in `transferFrom`. When a validation names a spender. |
| `TRACKER` | `afterTransfer(ctx)`      | Nothing. It records what it needs.             | After every movement the register records.                                         |

A module can declare one, two or all three types. The compliance keeps one list per type, so each call goes to exactly the modules that answer it. A token with no `SPENDER` module pays nothing for the spender check, and a token with no `RULE` module allows every movement that passes the eligibility gate.

```mermaid
flowchart TB
    T["Token"]
    T -->|"before a transfer or a mint"| CT["canTransfer"]
    T -->|"before spending an allowance"| CS["canSpenderCall"]
    T -->|"after a movement"| H["transferred, created, destroyed"]
    CT --> RU["RULE modules<br/>smallest allowed amount wins"]
    CS --> SP["SPENDER modules<br/>all must agree"]
    H --> L["Compliance ledger updated"]
    L --> TR["TRACKER modules told"]
```

## The context

Every call to a module carries the same structure, filled in once by the compliance.

| Field                        | Meaning                                                                                                                                                                                                   |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `compliance`                 | The compliance asking. A module reads the ledger and its own settings from this address.                                                                                                                  |
| `fromIdentity`, `toIdentity` | The identities of the sender and the recipient, already resolved. Zero for the absent side of a mint or a burn.                                                                                           |
| `fromWallet`, `toWallet`     | The addresses involved, as keys: an address of the reference chain, or the key of a satellite wallet.                                                                                                     |
| `amountMin`, `amountMax`     | The range of the movement. Equal for a movement on the reference chain. A range when a cross-chain validation is being issued.                                                                            |
| `isIssuance`                 | True while a cross-chain validation is being issued.                                                                                                                                                      |
| `spender`                    | Who executes on the sender's behalf, as an interoperable address. Filled in the calls to `SPENDER` modules only. Empty in the calls to `RULE` and `TRACKER` modules, and when the sender executes itself. |

Two consequences for whoever writes a module:

* **A module never resolves an identity.** Both arrive resolved. Every module reasons about the same holders.
* **One convention covers every case.** A mint has no sender, a burn has no recipient, and the same function handles a movement on the reference chain and the issuance of a validation for a satellite.

## How a rule answers

A `RULE` module does not say yes or no. It says how much: the largest amount it allows to move. No limit is the maximum value. A refusal is zero. The compliance keeps the smallest answer over all bound rules.

That single answer serves both situations.

| Situation                                | How the answer is used                                                                                              |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| A movement on the reference chain        | The amount must not exceed the smallest answer.                                                                     |
| The issuance of a cross-chain validation | The requested range is narrowed to the smallest answer. If nothing is left of the range, the validation is refused. |

A rule must be monotonic: a smaller amount is never less acceptable than a larger one. Caps, floors, limits and allow lists all are. A rule that is not, such as "a multiple of 100", evaluates the amount itself and answers all or nothing, and refuses a validation whose range is not a single amount.

A rule is read-only. The compliance calls it in a way that makes any write revert. A rule that needs to remember something also declares `TRACKER` and records it after the movement.

## The modules that ship

| Module                        | Type      | What it enforces                                                                                                                                              | Settings                                            |
| ----------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| `MaxBalancePerIdentityModule` | `RULE`    | A cap on what one identity may own, over all its addresses and all chains. It counts the identity's position and what open validations already promise to it. | `setMaxBalance(max)`                                |
| `SpenderVerificationModule`   | `SPENDER` | The spender must be eligible for the asset, like a holder.                                                                                                    | None                                                |
| `SpenderWhitelistModule`      | `SPENDER` | The spender must be on a list of operators named by the Issuer. The list starts empty, so every spender is refused until one is listed.                       | `allowSpender(spender)`, `disallowSpender(spender)` |

The cap is a complete example of a rule over the global distribution. It limits no burn and no movement between two addresses of one identity. The same logic covers a transfer on the reference chain, the issuance of a validation, two validations competing for the same room, and a settlement that arrives late.

The two spender modules are described in [Identity-based control and spender compliance](/token-and-register/identity-based-control-and-spender-compliance.md).

Other rules can be written on the same interface: a limit on the number of holders, a restriction by jurisdiction, a limit on supply, a lock-up. The modules of earlier versions of T-REX do not run on this interface, which changed in depth: each rule is implemented anew for it, and the three modules above are the ones that exist so far. Some rules need a check against data held off-chain. Those rely on Chainlink ACE. Writing a module is covered under Build.

## Binding and removing modules

All of these require the `OWNER` role on the compliance.

| Function                               | Effect                                                                                      |
| -------------------------------------- | ------------------------------------------------------------------------------------------- |
| `addModule(module)`                    | Binds a module.                                                                             |
| `addAndSetModule(module, calls)`       | Binds a module and configures it in the same transaction, with up to five calls.            |
| `callModuleFunction(callData, module)` | Configures a bound module. A module accepts settings only from a compliance it is bound to. |
| `removeModule(module)`                 | Unbinds a module.                                                                           |
| `forceRemoveModule(module)`            | Removes a module without calling it.                                                        |
| `resyncModuleTypes(module)`            | Re-reads the types a module declares, after the module was upgraded.                        |

`getModules()` lists the bound modules, `getModulesByType(type)` those of one type, and `isModuleBound(module)` answers for one.

The rules of binding:

* **At most 25 modules** per compliance.
* **A module declares at least one type, and each type once.** A module that declares nothing would never be called, and is refused.
* **A module can refuse a compliance.** A module that needs a precondition says so, and the binding fails until the precondition is met. The three modules that ship bind anywhere.
* **A module's types are read when it is bound.** If an upgrade of the module changes them, the change takes effect when the `OWNER` calls `resyncModuleTypes`.

### Settings belong to a binding

A module is one contract that serves many compliances. It keeps its settings per compliance, and ties them to the current binding.

* **Unbinding discards the settings.** A module removed and bound again comes back with an empty list or an unset cap.
* **Do not remove and re-add a module to refresh it.** Use `resyncModuleTypes`.
* **Bind a restrictive module with its settings.** `SpenderWhitelistModule` refuses every spender until one is listed. Binding it with `addAndSetModule` lists the operators in the same transaction, so there is no interval in which `transferFrom` is blocked for everyone.

### When a module misbehaves

| Situation                                          | Effect                                                                       | Remedy                                                                                                                                             |
| -------------------------------------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| A `TRACKER` module reverts when told of a movement | The movement reverts. A module that cannot record a movement has to stop it. | Fix the cause, or remove the module.                                                                                                               |
| A module reverts when it is unbound                | `removeModule` reverts and the module stays bound.                           | `forceRemoveModule`, which does not call the module. The same module address cannot be bound to this compliance again. A new deployment of it can. |

A faulty module cannot hold a token hostage.

## Who controls a module's code

A module sits behind its own upgradeable proxy and has its own authority, separate from the token suite that binds it.

> **Trust statement.** Whoever can upgrade a module can change the rule for every compliance bound to it. The shared modules are deployed and governed by Network governance. An Issuer that wants sole control over a rule deploys its own instance of the module.

## Roles

| Role                 | Gates                                                                                                       |
| -------------------- | ----------------------------------------------------------------------------------------------------------- |
| `OWNER`              | Binding, configuring and removing modules. Repairing a position.                                            |
| `COMPLIANCE_MANAGER` | The settings of cross-chain validations: validity window, reconciliation windows, issuance pause per chain. |
| `AGENT`              | Requesting a cross-chain validation on behalf of a holder.                                                  |
| `VALIDATION_KEEPER`  | Discarding expired validations and resolving stuck ones. The T-REX Network runs the keeper under this role. |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.t-rex.network/token-and-register/modular-compliance.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
