> 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/build/integrate-a-venue-or-an-application.md).

# Integrate a venue or an application

A venue or an application moves tokens that belong to others: it settles a trade, holds a deposit, lends against a position, or distributes to a list of holders. A T-REX token is an ERC-20, and `approve`, `permit` and `transferFrom` work as a contract expects. Four things differ. This page covers them for teams that build a trading venue, a settlement or escrow contract, a lending or distribution application.

| In a plain ERC-20                                         | In a T-REX token                                                                                                                                                       |
| --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| An allowance is all a spender needs.                      | The asset can restrict which spenders it accepts. The spender is checked before the allowance is spent.                                                                |
| Any address can receive tokens, a contract included.      | The recipient must be eligible. A contract that holds tokens is a holder: it has an identity with the claims the asset requires, and the rules count it.               |
| A transfer within the balance and the allowance succeeds. | Each transfer also passes the pause, the freezes, the eligibility of the recipient and the rules of the asset, when it executes. A transfer of zero reverts.           |
| Only a contract's own code moves the tokens it holds.     | The Issuer's agents can freeze, force a transfer out of, recover or burn what a contract holds. The contract's own ONCHAINID can move it too, with `identityTransfer`. |

Applications built on the protocol are listed in the [T-REX AppStore](/appstore.md).

## Read the register

Token functions take addresses. Ownership, eligibility and the rules are evaluated on the identity an address resolves to. The registry of an asset is `Token.identityRegistry()`, its compliance is `Token.compliance()`.

| Question                                                            | Call                                                               |
| ------------------------------------------------------------------- | ------------------------------------------------------------------ |
| Which identity is behind this address, for this asset?              | `TREXRegistry.identity(address)`                                   |
| Can this counterparty receive the asset?                            | `TREXRegistry.isVerified(address)`                                 |
| How much can this address deliver?                                  | `Token.freeBalanceOf(address)`: the balance less the frozen tokens |
| What does this identity own, over all its addresses and all chains? | `ModularCompliance.positionOf(identity)`                           |

Key counterparties by identity: two addresses that resolve to one ONCHAINID are one holder, and a transfer between them changes no position. Size orders on the free balance, not on `balanceOf`. The parts of a balance, the wallets of an identity and their status are in [Integrate a wallet or a custodian](/build/integrate-a-wallet-or-a-custodian.md).

## Execute for a holder

The holder gives the contract an allowance, with `Token.approve(spender, amount)` or with a signature the contract submits to `Token.permit(owner, spender, value, deadline, v, r, s)`. Giving the allowance is plain ERC-20: the spender policy is not asked. The contract then calls `Token.transferFrom(from, to, amount)`.

The token checks, in this order:

1. **The spender.** `ModularCompliance.canSpenderCall(spender, from, to, amount)` returns true for the caller. Otherwise `SpenderNotAllowed`, and the allowance is untouched.
2. **The allowance.** It covers the amount, and is spent. Otherwise `ERC20InsufficientAllowance`.
3. **The pause.** The token is not paused. Otherwise `EnforcedPause`.
4. **The freezes.** `from` is not frozen, then `to` is not frozen. Otherwise `FrozenWallet`, which names the address.
5. **The free balance.** The amount is within the balance of `from` less its frozen tokens. Otherwise `ERC20InsufficientBalance`.
6. **Eligibility of the recipient.** `TREXRegistry.isVerified(to)` returns true. Otherwise `UnverifiedIdentity`.
7. **The rules.** `ModularCompliance.canTransfer(from, to, amount)` returns true. Otherwise `ComplianceNotFollowed`.
8. **The amount.** The balances move, and the token tells the compliance, which does not record a movement of zero. Otherwise `ZeroValue`.

The compliance then moves the amount from the position of the sender's identity to the position of the recipient's identity. The token does not check the eligibility of the sender: a holder whose claim expired can still send. The flow is in [10 Transfer between investors](/guides/positions/10.-transfer-between-investors.md).

## Meet the spender policy of the asset

`canSpenderCall` is the whole policy. With no module of type `SPENDER` bound to the compliance, it returns true for every spender, as with any ERC-20. Otherwise the compliance encodes the spender as an ERC-7930 interoperable address of the reference chain and calls `moduleCheckSpender` on every `SPENDER` module, with the sender, the recipient and the amount. Every module must accept, and the answer holds for one movement. Two modules ship, and an asset binds one, both or none.

| Module bound                | The spender is accepted when                                                                                                                                                                                                                                                                                                             | What the venue does                                                                                     |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `SpenderVerificationModule` | `TREXRegistry.isWalletVerified(spender)` is true: the spender's address resolves to an identity, its link is not revoked, and the identity holds a valid claim from a trusted Claim Issuer for every topic required of its type. When the Issuer has disabled the eligibility checks of the registry, the read is true for every spender | Holds an ONCHAINID with the claims the asset requires                                                   |
| `SpenderWhitelistModule`    | `isSpenderAllowed(compliance, spender)` is true: the spender's interoperable address is on the list of named operators the Compliance Owner keeps for this asset. The list starts empty. The spender's identity is not read                                                                                                              | Asks the Issuer to list the contract: [26 Authorize spenders](/guides/issuer/26.-authorize-spenders.md) |

Both reads take the spender as an ERC-7930 interoperable address in canonical form (`bytes`), where `canSpenderCall` takes the address.

An application finds the policy of an asset with two reads:

```solidity
IModularCompliance compliance = IModularCompliance(address(token.compliance()));
address[] memory policy = compliance.getModulesByType(IModule.ModuleType.SPENDER);
// empty: no policy. Otherwise IModule(policy[i]).name() returns the name of each module
bool accepted = compliance.canSpenderCall(spender, from, to, amount);
```

An Issuer can also bind a `SPENDER` module of its own, so `canSpenderCall` is the answer to rely on. A refused `transferFrom` reverts with `SpenderNotAllowed(spender, from, to, value)`. See [Identity-based control and spender compliance](/token-and-register/identity-based-control-and-spender-compliance.md).

## Give the contract an identity

A venue contract takes part under its own ONCHAINID, of type `SMART_CONTRACT`. The identity is bound to the contract's address and to no other, and it carries the claims the asset requires, like the identity of any participant. This type is created under a role granted by Network governance, never self-deployed: see [Identity types and factory gating](/identity-onchainid/identity-types-and-factory-gating.md).

An Issuer can require its own set of claim topics from `SMART_CONTRACT` identities: `TREXRegistry.getClaimTopicsForIdentityType(6)` returns it, and when it is empty the default topics of `getClaimTopics()` apply.

What the asset checks depends on what the contract does with the tokens.

| The contract                                                              | It is     | The asset checks                                                                                                                                                                             |
| ------------------------------------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Calls `transferFrom` from one holder to another and never receives tokens | A spender | The spender policy                                                                                                                                                                           |
| Receives tokens: an escrow, a pool, a vault                               | A holder  | Eligibility: `isVerified` on the contract's address, on every transfer to it. The rules, which count it as a holder. And the spender policy, when it pulls the tokens in with `transferFrom` |

What follows from being a holder:

* **The contract's identity has a position.** On the register, tokens in escrow belong to the identity of the escrow, not to the depositor.
* **Rules apply to that position.** `MaxBalancePerIdentityModule` caps what one identity owns, with no exemption: all the deposits a contract holds are one position under the cap.
* **Agent actions land on its address.** A freeze on it immobilizes every deposit.
* **The keys of the contract's identity command its tokens.** The ONCHAINID the contract is bound to can call `Token.identityTransfer(from, to, amount)` with the contract's address as `from`. No allowance is read and the contract's code does not run. Whoever holds a `MANAGEMENT` or an `ACTION` key on that identity can move every deposit, within the checks of a transfer, so those keys are part of the security model of the escrow.

## Choose a settlement pattern

Atomic delivery versus payment is a property of the application's own contract: both legs execute in one transaction, and a revert of the token leg reverts the other. The token guarantees that a transfer passes every check and is recorded, or reverts and changes nothing.

### Pull from the seller to the buyer

One `transferFrom(seller, buyer, amount)`. The contract never holds the tokens, and the position moves from the seller's identity to the buyer's.

* **Checks:** the eight checks above, once, at settlement. The contract is a spender only.
* **Fails when:** any of the eight fails. Nothing reserves the seller's tokens between the order and the settlement: the allowance can be lowered and the free balance can drop.

### Escrow

Two transfers, with the contract holding the tokens in between.

| Moment  | Call                                                                                                                       | Checks                                                                                                                                      |
| ------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| Deposit | `transferFrom(seller, escrow, amount)` by the contract. Or `transfer(escrow, amount)` by the seller, with no spender check | The escrow is the recipient: eligible, not frozen, and the rules count the deposit in its position                                          |
| Release | `transfer(buyer, amount)` by the contract                                                                                  | The escrow is the sender: no spender check, not frozen, free balance sufficient. The buyer is the recipient: eligible, and within the rules |
| Return  | `transfer(seller, amount)` by the contract                                                                                 | The same, with the seller as recipient                                                                                                      |

* **Fails when:** at deposit, the escrow lacks a claim or its position reaches a cap. At release, the buyer is no longer eligible, a rule refuses the buyer, the escrow is frozen, or the asset is paused. At return, the seller lost eligibility since the deposit.

### Batch

`Token.batchTransfer(toList, amounts)` sends from the caller's own balance to several recipients, for a contract that holds the tokens it distributes. `transferFrom` has no batch form.

* **Checks:** every entry is a `transfer` and passes every check.
* **Fails when:** the two lists differ in length (`ArrayLengthMismatch`), or one entry fails, which reverts the whole batch. Two entries to one identity can each pass `canTransfer` before the batch, and the second can still exceed a cap.

## Check before submitting

Before a `transferFrom`, read what the token checks: `canSpenderCall(spender, from, to, amount)`, `allowance(from, spender)`, `paused()`, `isFrozen` on both addresses, `freeBalanceOf(from)`, `isVerified(to)` and `canTransfer(from, to, amount)`.

`UtilityChecker.getTransferStatus(token, from, to, amount)` answers all but the first two in one call. It does not know the spender or the allowance, and it is written for off-chain calls: see [Pre-flight checks with UtilityChecker](/build/pre-flight-checks-with-utilitychecker.md). A contract that checks on-chain reads `canSpenderCall`, `isVerified` and `canTransfer` directly.

A check is a read at one moment. The token evaluates everything again when the transfer executes. Map each error to an action:

| Error                                               | The application                                                                                               |
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `SpenderNotAllowed`                                 | Stops settling the asset until its policy accepts the contract                                                |
| `ERC20InsufficientAllowance`                        | Asks the holder for a new approval                                                                            |
| `EnforcedPause`                                     | Suspends settlement for the asset until `Unpaused`                                                            |
| `FrozenWallet`                                      | Cancels the orders of the address the error names                                                             |
| `UnverifiedIdentity`                                | Cancels for this recipient until it is eligible again                                                         |
| `ERC20InsufficientBalance`, `ComplianceNotFollowed` | Reduces the amount, or cancels. `UtilityChecker.getTransferDetails` gives the largest amount each rule allows |
| `ZeroValue`                                         | Never submits an amount of zero                                                                               |

## What can change under an application

| What changes                                                                                                 | Effect on the application                                                                                                                    | Follow                                                                                                                                                                                                                                  |
| ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The asset is paused                                                                                          | No order settles and no escrow releases                                                                                                      | `Paused(account)`, `Unpaused(account)`                                                                                                                                                                                                  |
| An address or an amount is frozen                                                                            | A frozen address neither sends nor receives. A frozen amount lowers the free balance                                                         | `AddressFrozen(address, isFrozen, agent)`, `TokensFrozen(address, amount)`, `TokensUnfrozen(address, amount)`                                                                                                                           |
| An agent forces a transfer out of the contract's address, recovers it to another address, or burns from it   | The contract's balance decreases without its code running, and no longer matches its own accounting                                          | A `Transfer` from its address that it did not make, followed by `ForcedTransfer(agent)` for a forced transfer, or by `RecoverySuccess(lostWallet, newWallet, identity)` for a recovery                                                  |
| The contract's ONCHAINID moves tokens out of its address                                                     | The same: the balance decreases without the contract's code running                                                                          | A `Transfer` from its address, followed by `IdentityTransfer(identity, from, to, amount)`                                                                                                                                               |
| A holder loses eligibility: a claim expires or is revoked. Or one of its addresses does: its link is revoked | The holder, or that address, can still send: sell, deposit, repay. It can no longer receive: a purchase, a release or a return to it reverts | `TREXRegistry.isVerified(address)`, read at settlement                                                                                                                                                                                  |
| The contract's own claims lapse                                                                              | Under `SpenderVerificationModule`, its `transferFrom` reverts. As a holder, it takes no deposit and can still release                        | `isVerified` on its own address                                                                                                                                                                                                         |
| The Issuer changes the spender policy or a rule                                                              | `transferFrom` reverts with `SpenderNotAllowed`, and allowances are of no use. Or an amount that passed is refused                           | `ModuleAdded(module)`, `ModuleRemoved(module)`, `ModuleForceRemoved(module)`, `ModuleTypesRecorded(module, moduleTypes)`, `ModuleInteraction(target, data)` on the compliance. `SpenderAllowed`, `SpenderDisallowed` on the list module |

An escrow reconciles what it owes with `freeBalanceOf` on its own address, and defines what happens when it holds less. A release or a return can be refused for a reason outside the contract: one blocked deposit must not block the others.

## On a satellite chain

On a satellite, a transfer executes only against a validation issued on the reference chain. A validation can name a spender: the party allowed to execute the transfer on the satellite for the sender.

* **The request.** `ModularCompliance.requestTransferValidation(from, to, requestedMin, requestedMax, spender)` is called by the identity of the sending wallet, or by a Token Agent. A venue asks to be named.
* **The spender.** `spender` is the ERC-7930 interoperable address of the venue on the satellite, in canonical form. It is empty when the sender executes itself.
* **The check.** No module runs on a satellite, so the same `SPENDER` modules judge the spender at issuance. A refusal reverts with `ValidationSpenderRefused`, and no validation is issued. Under the list policy, the Issuer lists the address the venue has on that chain.

The flow is in [31 Transfer on a satellite](/guides/cross-chain/31.-transfer-on-a-satellite.md).

## Checklist

* `canSpenderCall` is read per transfer, and the policy of each asset is known from `getModulesByType`.
* The contract has an ONCHAINID of type `SMART_CONTRACT`, with the claims of each asset it serves.
* Every `transferFrom` is checked first, each error is mapped to an action, and zero is never submitted.
* A contract that holds tokens is one holder under the rules, caps included, and a refused release or return has a way out.
* Pause, freezes, forced transfers, recoveries, burns and changes of modules are indexed, and the keys of the contract's identity are held as tightly as the contract's own.
* On a satellite, the venue is named as `spender`, with the address it has on that chain.


---

# 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/build/integrate-a-venue-or-an-application.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.
