> For the complete documentation index, see [llms.txt](https://docs.selcier.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.selcier.xyz/build-on-selcier/transfer-outcomes.md).

# Transfer outcome codes

A lookup for the codes a Selcier transfer check can return, covering their origin, their meaning and what a user or developer can do about each.

A Selcier Share Token explains itself when a transfer would not go through. The call to make is `canTransfer(from, to, amount)`. On failure its result is `(false, reasonCode)`, and the second element is a string stored as `bytes32`. You will meet that same value in three other places:

* `POST /v1/transfers/check`, in the `reason_code` field
* API errors of type `rule_violation`, in the `code` field
* The chain, as the argument to the custom error `TransferNotAllowed(bytes32 reasonCode)` when a transfer, transfer from or mint is rejected

The sequence of checks never varies: token state, then identity, then the enabled modules in whatever order the issuer has configured. Evaluation stops at the first failure. A transfer with two problems therefore reports only one, and the second appears after the first is fixed.

## Code lookup

### Token and identity checks

| Code                            | Origin   | What happened                                                                                                                            | What to do                                                                                                            | Message you could show                                                 |
| ------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| `OK`                            | Token    | All checks passed                                                                                                                        | No action needed                                                                                                      |                                                                        |
| `TOKEN_PAUSED`                  | Token    | A pause is in force on the share class, set by the issuer or registrar                                                                   | Hold off until it is unpaused. The reason for the pause is on-chain                                                   | "The company has paused transfers for this share class."               |
| `FROZEN`                        | Token    | The wallet has a scheduled recovery, or its unfrozen balance falls short of the amount because some tokens are frozen                    | Get in touch with the company. Frozen tokens never leave the wallet, and they cannot be moved                         | "Part of your balance is frozen. Please ask the company for details."  |
| `INSUFFICIENT_UNLOCKED_BALANCE` | Token    | The sender's balance is smaller than the amount                                                                                          | Lower the amount. To see what can move after freezes, holding periods and vesting too, read `unlockedBalanceOf(from)` | "Right now you can transfer up to {unlocked} shares."                  |
| `SENDER_NOT_VERIFIED`           | Identity | Either no identity is linked to the sending wallet, or that identity is missing an `IDENTITY_VERIFIED` or `SCREENED` claim               | Finish verification in Portfolio, or connect the wallet to an identity you already have                               | "To transfer shares from this wallet, verify your identity first."     |
| `RECIPIENT_NOT_VERIFIED`        | Identity | Either no identity is linked to the receiving wallet, or that identity is missing an `IDENTITY_VERIFIED` or `SCREENED` claim             | Verification has to be finished on the recipient's side beforehand                                                    | "This recipient has to verify their identity before receiving shares." |
| `CLAIM_EXPIRED`                 | Identity | For the sender or recipient, an `IDENTITY_VERIFIED` or `SCREENED` claim has lapsed or comes from a verifier that has lost trusted status | Verify again. A notice goes to holders 30 days ahead of expiry                                                        | "Your verification has lapsed. Please renew it to carry on."           |

### Module checks

| Code                     | Origin                  | What happened                                                                                                                      | What to do                                                                                                    | Message you could show                                                             |
| ------------------------ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `REGION_BLOCKED`         | `RegionModule`          | The share class does not permit the recipient's region, or no valid `REGION` claim is on file for the recipient                    | The platform offers no fix, because the allowed regions are the company's decision                            | "This transfer can't be completed in the recipient's region."                      |
| `NOT_ELIGIBLE`           | `EligibilityModule`     | A valid `INVESTOR_ELIGIBLE` claim at the required tier is absent for the recipient                                                 | Have the recipient finish the eligibility check in Portfolio                                                  | "The recipient can't hold this share class yet because eligibility is incomplete." |
| `HOLDING_PERIOD_ACTIVE`  | `HoldingPeriodModule`   | A holding period still covers the tokens being sent                                                                                | Wait for the lots to unlock. Unlock dates per lot come from `GET /v1/holders/{id}/lots`                       | "You can transfer these shares starting {date}."                                   |
| `HOLDER_CAP_REACHED`     | `HolderCapModule`       | The share class is at its holder maximum and this transfer would bring in one more                                                 | Send to someone who already holds, or wait until the company adjusts the cap                                  | "No more holders can be added to this share class."                                |
| `WINDOW_CLOSED`          | `TransferWindowModule`  | Nothing is open at the moment: this share class moves only in a Liquidity Window or an approved trading period                     | Wait for the next one. Portfolio lists the windows coming up                                                  | "Transfers reopen at the company's next Liquidity Window."                         |
| `ROFR_PENDING`           | `ROFRModule`            | One of three things: this transfer has no matching proposal, the notice period is still running, or the right holder has exercised | Submit the transfer through `propose(to, amount)`, then wait for a waiver or for the notice period to run out | "Until {date}, the company has the first right to buy these shares."               |
| `MAX_OWNERSHIP_EXCEEDED` | `MaxOwnershipModule`    | The per-holder limit would be exceeded on the recipient's side                                                                     | Lower the amount                                                                                              | "This amount would put the recipient over the holding limit for this share class." |
| `UNVESTED`               | `EmployeeVestingModule` | Looking across the sender's identity, the transfer reaches into shares still waiting to vest                                       | Wait for the schedule to progress. Portfolio shows what remains unvested                                      | "Vesting for these shares isn't complete."                                         |

## Working with codes in an app

* **Find problems ahead of signing.** Run `canTransfer` or `POST /v1/transfers/check` first. Reverting on-chain burns gas, and a clear explanation up front serves the user far better.
* **Put words in the interface, not codes.** Each check comes back with a plain-language `message` from the API. Display that, or adapt the sample messages from the tables above. Raw codes belong in logs and developer tools.
* **Repeat the check right before submitting.** The inputs drift: holder counts shift, claims lapse, windows shut. A transfer screen left open for a while deserves a second check at the last moment.
* **Plan for codes you have not seen.** A module added later can bring reason codes of its own. Fall back to a generic failure state and the API's `message` for anything unrecognized.

## Reading a code from chain data

On-chain, a code is an ASCII string padded on the right with zeros up to 32 bytes. This is how to read one using viem:

```ts
import { hexToString } from "viem";

const code = hexToString(reasonCode, { size: 32 }).replace(/\0+$/, "");
```

`decodeReasonCode` in the SDK wraps that same logic, and the SDK also exports all the codes as constants, as described under [TypeScript client](/build-on-selcier/typescript-client.md). To learn what lies behind each code, see [Compliance modules](/fundamentals/compliance-modules.md).


---

# 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.selcier.xyz/build-on-selcier/transfer-outcomes.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.
