> 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-wallet-or-a-custodian.md).

# Integrate a wallet or a custodian

A T-REX token is an ERC-20. `balanceOf`, `transfer`, `approve` and `transferFrom` behave as a wallet expects, and a wallet that supports ERC-20 displays and sends the token without any change. Four things differ from a plain ERC-20, and a wallet or a custody platform that ignores them shows numbers that are not true or lets a holder sign a transaction that reverts. This page covers those four things and what to build for each.

| In a plain ERC-20                                 | In a T-REX token                                                                                                                                         |
| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The holder is an address.                         | The holder is an identity, an ONCHAINID. An address resolves to it. Several addresses can belong to one holder, and the identity can hold tokens itself. |
| A balance is one number.                          | A balance has parts: free, frozen, and, for an asset distributed on satellites, bridged and reserved.                                                    |
| A transfer succeeds if the balance is sufficient. | A transfer also requires an eligible recipient and a movement the rules of the asset allow.                                                              |
| Only the holder moves its tokens.                 | The Issuer's agents can freeze, force a transfer, recover and burn, under roles.                                                                         |

## Show who holds

Token functions take addresses. Ownership and eligibility are evaluated on the identity an address resolves to.

| Question                                                     | Call                                                                                                                                                                                          |
| ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Which identity does this address resolve to, for this asset? | `TREXRegistry.identity(address)`                                                                                                                                                              |
| Is this address eligible to receive this asset?              | `TREXRegistry.isVerified(address)`                                                                                                                                                            |
| Which identity is this address linked to, for every asset?   | `IdentityFactory.getIdentity(account)`, with `account` the address as an ERC-7930 interoperable address                                                                                       |
| Is this wallet active, revoked, or never linked?             | `IdentityFactory.getAccountStatus(account)`                                                                                                                                                   |
| Which wallets are actively linked to this identity?          | `IdentityFactory.getAccountsCount(identity)` and `getAccounts(identity, start, end)`. Revoked wallets, the identity's own address and addresses registered for one asset only are not listed. |
| What does this identity own in total, for this asset?        | `ModularCompliance.positionOf(identity)`                                                                                                                                                      |

The registry of an asset is `Token.identityRegistry()`, and its compliance is `Token.compliance()`.

What to build:

* **Group addresses by identity.** A holder with three linked wallets has one position. Show the total and the detail per address.
* **Treat an ONCHAINID as a holder address.** It is a smart account and can hold tokens at its own address, with no wallet involved.
* **Show the state of each wallet.** A revoked wallet still holds what it held and can no longer receive. Follow `AccountLinked` and `AccountRevoked` on the `IdentityFactory` to keep the list of a holder's wallets, revoked ones included.

## Show balances that are true

| Figure         | Call                             | Meaning                                                                                                                                                                             |
| -------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Balance        | `Token.balanceOf(address)`       | The tokens at this address on the reference chain, frozen tokens included.                                                                                                          |
| Frozen         | `Token.getFrozenTokens(address)` | The part an agent has frozen. It cannot be sent.                                                                                                                                    |
| Free           | `Token.freeBalanceOf(address)`   | Balance minus frozen: what the address can send.                                                                                                                                    |
| Address frozen | `Token.isFrozen(address)`        | True when the whole address is frozen: it neither sends nor receives in a transfer between holders. An agent can still mint to it, burn from it, or force a transfer to or from it. |
| Asset paused   | `Token.paused()`                 | True when transfers between holders are halted.                                                                                                                                     |
| Bridged        | `Token.bridgedBalanceOf(wallet)` | The position that is active on a satellite for this wallet. `wallet` is an interoperable address. These tokens are not in `balanceOf` on the reference chain.                       |
| Reserved       | `Token.reservedOf(wallet)`       | The part of a bridged position promised to transfers that are authorized and not settled.                                                                                           |
| Available      | `Token.availableOf(wallet)`      | Bridged minus reserved: what a new transfer on the satellite can draw on.                                                                                                           |

What to build:

* **Display the free balance as the spendable amount,** and the frozen amount next to it. A wallet that shows `balanceOf` alone lets a holder try to send tokens it cannot send.
* **For an asset open on satellites, show where the position is.** The balance on the reference chain and the bridged position on each satellite add up to what the holder owns. `ModularCompliance.positionOf(identity)` gives the total.
* **Do not read `totalSupply` as the sum of balances on the reference chain.** It includes the positions that are active on satellites.

The parts of a balance are described in [The balance model](/token-and-register/the-balance-model.md).

## Check a transfer before sending it

A transfer the token refuses reverts. Nothing moves and the holder pays for the failed transaction. Check first, and tell the holder why.

### One call

`UtilityChecker` is a read-only contract that answers for any token. Its answers, their edge cases and what they leave out are in [Pre-flight checks with UtilityChecker](/build/pre-flight-checks-with-utilitychecker.md).

| Call                                          | Returns                                                                                                                                 |
| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `getTransferStatus(token, from, to, amount)`  | Three booleans: the transfer passes the pause and freeze conditions, the recipient is eligible, the rules of the asset allow it.        |
| `getFreezeStatus(token, from, to, amount)`    | Whether a freeze blocks the transfer, and the balance the sender can use.                                                               |
| `getVerifiedDetails(token, address)`          | One entry per required topic: whether a valid claim from a trusted issuer exists, and from which issuer. Also the status of the wallet. |
| `getTransferDetails(token, from, to, amount)` | For each rule of the asset, the largest amount it allows and whether the requested amount fits.                                         |

### Or the checks themselves

`Token.paused()`, `Token.isFrozen(from)` and `Token.isFrozen(to)`, `Token.freeBalanceOf(from)`, `TREXRegistry.isVerified(to)`, `ModularCompliance.canTransfer(from, to, amount)`, and for a spender `ModularCompliance.canSpenderCall(spender, from, to, amount)`.

### What to tell the holder

| The transaction would revert with | Say                                                                                                                                                     |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `EnforcedPause`                   | The asset is halted by its issuer. Try again later.                                                                                                     |
| `FrozenWallet`                    | This address, or the recipient's, is frozen by the issuer's agents.                                                                                     |
| `ERC20InsufficientBalance`        | The amount exceeds the free balance. Part of the balance is frozen, or active on a satellite.                                                           |
| `UnverifiedIdentity`              | The recipient is not eligible for this asset: it has no identity, its wallet link was revoked, or a required credential is missing, expired or revoked. |
| `ComplianceNotFollowed`           | A rule of the asset refuses this amount for this pair of holders. A smaller amount may pass.                                                            |
| `SpenderNotAllowed`               | The asset does not accept this spender.                                                                                                                 |
| `ERC20InsufficientAllowance`      | The spender is not approved for this amount.                                                                                                            |
| `ZeroValue`                       | The amount is zero. The token refuses it.                                                                                                               |

A check is a read at one moment. Eligibility and rules are evaluated again when the transfer executes, so a transfer that passed the check can still revert if something changed in between.

## Send

| Way                                   | Call                                                                                            | When                                                                                                                                                              |
| ------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| From the wallet that holds the tokens | `Token.transfer(to, amount)`                                                                    | The usual case.                                                                                                                                                   |
| Through a spender                     | `Token.approve(spender, amount)`, then the spender calls `Token.transferFrom(from, to, amount)` | A venue, a settlement contract, a custodian. The asset can restrict which spenders it accepts: see [26](/guides/issuer/26.-authorize-spenders.md).                |
| With a signed approval                | `Token.permit(owner, spender, value, deadline, v, r, s)`                                        | The holder approves with a signature and no transaction of its own (ERC-2612).                                                                                    |
| Through the identity                  | `Token.identityTransfer(from, to, amount)`, called by the ONCHAINID                             | The identity moves the free tokens of a wallet linked to it, with no signature from that wallet: see [24](/guides/identity/24.-transfer-through-the-identity.md). |
| From the identity itself              | The ONCHAINID calls `Token.transfer(to, amount)`                                                | For tokens held at the identity's own address. The call is a user operation, or an execution ordered by a key of the identity.                                    |

Every way passes the same gates. The steps and the checks are in [10 Transfer between investors](/guides/positions/10.-transfer-between-investors.md).

## Hold for clients

A custody platform chooses how its clients appear on the register. The choice decides who the protocol treats as the holder.

| Model          | How it is recorded                                                                                                                                                         | Who the register shows as holder                                                 |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| **Segregated** | Each client has its own ONCHAINID. Each custody address is linked to the client's identity, or registered to it by the Issuer's agent when the address cannot link itself. | The client                                                                       |
| **Omnibus**    | One address holds for many clients. It is recorded under the identity of the custodian that operates it.                                                                   | The custodian. The clients behind the position are in the custodian's own books. |

What follows from the choice:

* **Eligibility is checked on the recorded holder.** In the segregated model each client needs the claims the asset requires. In the omnibus model the custodian's identity does.
* **Rules count the recorded holder.** A cap per investor, or a limit on the number of holders, sees an omnibus position as one holder.
* **Agent actions land on the recorded address.** A freeze on an omnibus address immobilizes every client behind it.

A custodian acts for a client in the segregated model in one of two ways:

* **A custody wallet attached to the client's identity.** The custodian holds the keys of the wallet and sends from it. A wallet that can sign the linking call is linked to the identity in the global identity registry, and every asset recognizes it: [07](/guides/identity/07.-link-a-wallet-to-an-onchainid.md). A wallet that cannot is registered for one asset by that asset's Issuer, in the asset's own registry: [06](/guides/agents/06.-register-an-investor-for-an-asset.md).
* **An `ACTION` key on the client's ONCHAINID.** The custodian moves the client's tokens through the identity, without holding the wallets: see [22](/guides/identity/22.-add-and-remove-keys-on-an-onchainid.md) and [24](/guides/identity/24.-transfer-through-the-identity.md). The client keeps `MANAGEMENT` and can remove the key.

## What can happen to a position without the holder

The Issuer's agents act on positions under roles, and none of it needs the holder's signature. A wallet that does not follow these events shows a holder a state it cannot explain.

| What happened                                  | Event on the `Token`                                               | What to show                                                                                                                   |
| ---------------------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
| The address was frozen or unfrozen             | `AddressFrozen(address, isFrozen, agent)`                          | The holder cannot send from it or receive on it, or can again                                                                  |
| An amount was frozen or unfrozen               | `TokensFrozen(address, amount)`, `TokensUnfrozen(address, amount)` | The new free balance                                                                                                           |
| An agent moved tokens                          | `Transfer(from, to, amount)`                                       | A movement the holder did not sign. A forced transfer can release frozen tokens first                                          |
| The position was recovered from a lost wallet  | `RecoverySuccess(lostWallet, newWallet, identity)`                 | The balance is at the new address                                                                                              |
| The identity moved tokens of a linked wallet   | `IdentityTransfer(identity, from, to, amount)`                     | A movement ordered by the holder's identity                                                                                    |
| Tokens were burned                             | `Transfer(address, 0x0, amount)`                                   | A redemption or a cancellation. A position that leaves for a satellite emits the same `Transfer`, together with `DelegatedOut` |
| The asset was paused or resumed                | `Paused(account)`, `Unpaused(account)`                             | Transfers are halted, or possible again                                                                                        |
| The position went to a satellite, or came back | `DelegatedOut(...)`, `Recalled(...)`                               | Where the position is now                                                                                                      |

Two changes come from outside the token and affect what a holder can do:

* **A claim is revoked or expires.** The holder keeps its position, can still send to an eligible recipient and can no longer receive. Read `TREXRegistry.isVerified(address)`.
* **A wallet is revoked by its owner.** `AccountRevoked(account, identity, caller)` on the `IdentityFactory`. The wallet can no longer receive any asset.

## On a satellite chain

On a satellite, the asset is represented by T-REX Lite, a plain ERC-20. A wallet displays and sends it like any ERC-20, with one difference: a transfer executes only against a validation that the reference chain issued for it. A holder first requests the validation, through its identity on the reference chain, then makes the transfer on the satellite. Without a validation, the transfer reverts.

What to build is in the cross-chain guides: taking a position to a satellite ([30](/guides/cross-chain/30.-delegate-a-position-out-to-a-satellite.md)), transferring there ([31](/guides/cross-chain/31.-transfer-on-a-satellite.md)), and bringing it back ([32](/guides/cross-chain/32.-recall-a-position-to-the-reference-chain.md)).

## Checklist

* The spendable amount shown is `freeBalanceOf`, not `balanceOf`.
* Addresses of one identity are grouped, and the ONCHAINID itself is treated as a holder address.
* Every transfer is checked before signing, and a refusal is explained in the holder's terms.
* A transfer of zero is not offered.
* Freezes, forced transfers, recoveries, burns and the pause are indexed and shown.
* For a custody platform: the model, segregated or omnibus, is chosen with each Issuer, since it decides who the register records as the holder.
* For an asset open on satellites: the bridged and reserved parts are shown, per 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-wallet-or-a-custodian.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.
