> ## Documentation Index
> Fetch the complete documentation index at: https://docs.trusset.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Contracts

> Contract architecture, roles, state management, and function reference for the Stock Token suite.

The Stock Token suite consists of three deployable contracts and two interfaces. This page covers architecture, state layout, and the complete function reference for each contract.

## Contract Relationships

`StockToken` holds a reference to `IdentityRegistryUpgradeable` and an optional `BasicComplianceModule`. On every transfer, the token contract calls `isVerified()` on the registry for both parties (unless one is an authorized contract), then calls `canTransfer()` on the compliance module if one is set.

The identity registry can itself hold references to multiple compliance modules via `addComplianceModule()`. When the registry's own `canTransfer()` is called (by external integrators checking compliance at the registry level), it iterates all registered modules. The token contract's direct compliance module reference is separate - it allows per-token compliance configuration independent of the registry-level modules.

## StockToken

UUPS-upgradeable ERC-3643 with transfer restrictions, corporate actions, and sub-issuer architecture.

### Initialization

```solidity theme={null}
function initialize(
    string memory name_,        // e.g. "Acme Corp Class A"
    string memory symbol_,      // e.g. "ACME-A"
    string memory isin_,        // ISIN identifier
    string memory metadataURI_, // Off-chain metadata URI
    address identityRegistry_,  // IdentityRegistry contract address
    address issuer_,            // Receives ISSUER_ROLE + DEFAULT_ADMIN_ROLE
    address legalOperator_      // Receives LEGAL_OPERATOR_ROLE (upgrade authority)
) external initializer
```

The initializer grants `DEFAULT_ADMIN_ROLE` and `ISSUER_ROLE` to the issuer address. `LEGAL_OPERATOR_ROLE` goes to a separate address representing the licensed legal entity. The split ratio starts at 1:1.

### Roles

<Expandable title="Role hierarchy and admin relationships">
  | Role                  | Admin role           | Purpose                        |
  | --------------------- | -------------------- | ------------------------------ |
  | `DEFAULT_ADMIN_ROLE`  | self                 | Root admin, held by issuer     |
  | `ISSUER_ROLE`         | `DEFAULT_ADMIN_ROLE` | Token lifecycle management     |
  | `SUB_ISSUER_ROLE`     | `ISSUER_ROLE`        | Scoped issuance and redemption |
  | `CONTROLLER_ROLE`     | `ISSUER_ROLE`        | Regulatory enforcement actions |
  | `LEGAL_OPERATOR_ROLE` | `DEFAULT_ADMIN_ROLE` | Upgrade authorization only     |

  `ISSUER_ROLE` manages sub-issuers and controllers directly via `addSubIssuer()`, `removeSubIssuer()`, `addController()`, and `removeController()`. These convenience functions wrap the underlying `AccessControl` grant/revoke.
</Expandable>

### State Variables

| Variable                      | Type                          | Description                                         |
| ----------------------------- | ----------------------------- | --------------------------------------------------- |
| `_isin`                       | `string`                      | ISIN identifier for the security                    |
| `_metadataURI`                | `string`                      | URI to off-chain metadata (prospectus, term sheet)  |
| `_identityRegistry`           | `IIdentityRegistry`           | KYC/AML verification contract                       |
| `_complianceModule`           | `IComplianceModule`           | Optional compliance rules (holding limits, lockups) |
| `_frozenTokens`               | `mapping(address => uint256)` | Frozen token amount per account                     |
| `_splitMultiplierNumerator`   | `uint256`                     | Cumulative split ratio numerator                    |
| `_splitMultiplierDenominator` | `uint256`                     | Cumulative split ratio denominator                  |
| `_authorizedContracts`        | `mapping(address => bool)`    | KYC-exempt contract whitelist                       |
| `_subIssuerMinted`            | `mapping(address => uint256)` | Net outstanding tokens per sub-issuer               |
| `_subIssuerCap`               | `mapping(address => uint256)` | Issuance cap per sub-issuer (0 = unlimited)         |

### Issuance

```solidity theme={null}
function issue(address to, uint256 amount, bytes32 reason)
    external onlyRole(SUB_ISSUER_ROLE) whenNotPaused nonReentrant
```

Mints `amount` tokens to `to`. The recipient must be identity-verified or an authorized contract. The sub-issuer's net outstanding counter increments by `amount`; if a cap is set and would be exceeded, the call reverts with `SubIssuerCapExceeded`.

The `reason` parameter is a `bytes32` code for audit trails - define your own encoding scheme or use standardized codes.

```solidity theme={null}
function batchIssue(
    address[] calldata recipients,
    uint256[] calldata amounts,
    bytes32 reason
) external onlyRole(SUB_ISSUER_ROLE) whenNotPaused nonReentrant
    returns (uint256 processedCount)
```

Issues tokens to up to 200 recipients in a single transaction. Processing stops early if gas drops below 80,000 per item. Emits `BatchIssueCompleted(processedCount, total, reason)` so callers can detect partial completion.

### Redemption

```solidity theme={null}
function redeem(uint256 amount, bytes32 reason)
    external whenNotPaused nonReentrant
```

Burns `amount` from the caller's balance. Restricted to `ISSUER_ROLE` and `SUB_ISSUER_ROLE` - regular holders cannot self-redeem (eWpG register control requirement). If the caller is a sub-issuer, their outstanding counter decrements; the call reverts with `SubIssuerRedemptionExceeded` if `amount` exceeds their net outstanding.

```solidity theme={null}
function redeemFrom(address account, uint256 amount, bytes32 reason)
    external onlyRole(SUB_ISSUER_ROLE) whenNotPaused nonReentrant
```

Burns tokens from a specific account. The sub-issuer's outstanding counter decrements, limiting how much any single sub-issuer can redeem. Only unfrozen (transferable) tokens can be redeemed.

### Transfer Compliance

The standard ERC-3643 `transfer` and `transferFrom` functions are overridden to call `_validateTransfer` before execution. This internal function calls `canTransfer` and reverts if the result is not `SUCCESS`.

```solidity theme={null}
function canTransfer(address from, address to, uint256 amount)
    public view returns (TransferRestrictionCode code, string memory reason)
```

Pre-checks whether a transfer would succeed. Returns a machine-readable `TransferRestrictionCode` and a human-readable reason string. The check order:

1. Contract paused? -> `PAUSED`
2. Sufficient balance? -> `INSUFFICIENT_BALANCE`
3. Sufficient transferable balance (after frozen)? -> `SENDER_TOKENS_FROZEN`
4. Sender verified? (skipped for authorized contracts) -> `SENDER_NOT_ELIGIBLE`
5. Receiver verified? (skipped for authorized contracts) -> `RECEIVER_NOT_ELIGIBLE`
6. Compliance module allows? -> `COMPLIANCE_REJECTED`

<Expandable title="TransferRestrictionCode enum">
  ```solidity theme={null}
  enum TransferRestrictionCode {
      SUCCESS,
      SENDER_NOT_ELIGIBLE,
      RECEIVER_NOT_ELIGIBLE,
      SENDER_TOKENS_FROZEN,
      RECEIVER_TOKENS_FROZEN,
      COMPLIANCE_REJECTED,
      PAUSED,
      INSUFFICIENT_BALANCE
  }
  ```
</Expandable>

### Token Freezing

```solidity theme={null}
function freezeTokens(address account, uint256 amount) external onlyRole(CONTROLLER_ROLE)
function unfreezeTokens(address account, uint256 amount) external onlyRole(CONTROLLER_ROLE)
```

Freezing locks a specified amount on an account. The `transferableBalance` of that account decreases accordingly. Frozen amounts cannot exceed the account's total balance. Unfreezing requires that the amount does not exceed the currently frozen amount.

### Force Transfer

```solidity theme={null}
function forceTransfer(
    address from, address to, uint256 amount, bytes32 reason
) external onlyRole(CONTROLLER_ROLE) nonReentrant
```

Moves tokens between addresses regardless of sender approval, for regulatory seizure or recovery. The recipient must still be identity-verified or an authorized contract. If the sender has frozen tokens, the frozen amount is reduced proportionally (by the lesser of `amount` and `frozen`).

### Stock Split

```solidity theme={null}
function stockSplit(
    uint256 numerator, uint256 denominator, address[] calldata holders
) external onlyRole(ISSUER_ROLE) whenNotPaused
```

Executes a forward stock split. Only forward splits are supported (`numerator` must exceed `denominator`). For each holder in the array, the contract calculates `(currentBalance * numerator) / denominator`, mints the difference, and adjusts frozen token amounts proportionally (floor division).

The cumulative split ratio is updated and reduced by GCD after each split. Query it via `splitRatio()`.

<Warning>
  Reverse splits are not supported because integer division would destroy fractional shares. Use redemption and re-issuance for reverse splits. If any holders are omitted from the array, their balances will not be adjusted - the issuer is legally required to maintain a complete holder register under eWpG.
</Warning>

### Configuration

| Function                            | Role          | Description                                              |
| ----------------------------------- | ------------- | -------------------------------------------------------- |
| `setIdentityRegistry(address)`      | `ISSUER_ROLE` | Change the identity registry contract                    |
| `setComplianceModule(address)`      | `ISSUER_ROLE` | Set or remove (pass `address(0)`) compliance module      |
| `setMetadataURI(string)`            | `ISSUER_ROLE` | Update off-chain metadata URI                            |
| `setSubIssuerCap(address, uint256)` | `ISSUER_ROLE` | Set net outstanding cap for a sub-issuer (0 = unlimited) |
| `addAuthorizedContract(address)`    | `ISSUER_ROLE` | Whitelist a contract for KYC-exempt transfers            |
| `removeAuthorizedContract(address)` | `ISSUER_ROLE` | Remove a contract from the whitelist                     |
| `pause()` / `unpause()`             | `ISSUER_ROLE` | Halt or resume all token operations                      |

### View Functions

| Function                        | Returns                                    |
| ------------------------------- | ------------------------------------------ |
| `isin()`                        | ISIN identifier                            |
| `metadataURI()`                 | Off-chain metadata URI                     |
| `identityRegistry()`            | Registry contract address                  |
| `complianceModule()`            | Compliance module address (zero if none)   |
| `frozenTokens(address)`         | Frozen amount for an account               |
| `transferableBalance(address)`  | Balance minus frozen tokens                |
| `splitRatio()`                  | Cumulative split numerator and denominator |
| `isSubIssuer(address)`          | Whether address holds `SUB_ISSUER_ROLE`    |
| `isController(address)`         | Whether address holds `CONTROLLER_ROLE`    |
| `subIssuerMinted(address)`      | Net outstanding tokens for a sub-issuer    |
| `subIssuerCap(address)`         | Cap for a sub-issuer (0 = unlimited)       |
| `subIssuerOutstanding(address)` | Both minted and cap in one call            |
| `isAuthorizedContract(address)` | Whether a contract is whitelisted          |

### Events

| Event                                                 | Emitted by                          |
| ----------------------------------------------------- | ----------------------------------- |
| `Issued(to, amount, reason, subIssuer)`               | `issue`, `batchIssue`               |
| `Redeemed(from, amount, reason, redeemer)`            | `redeem`, `redeemFrom`              |
| `ForceTransfer(from, to, amount, reason, controller)` | `forceTransfer`                     |
| `TokensFrozen(account, amount)`                       | `freezeTokens`                      |
| `TokensUnfrozen(account, amount)`                     | `unfreezeTokens`                    |
| `StockSplit(numerator, denominator, newTotalSupply)`  | `stockSplit`                        |
| `BatchIssueCompleted(processed, total, reason)`       | `batchIssue`                        |
| `IdentityRegistryUpdated(old, new)`                   | `setIdentityRegistry`               |
| `ComplianceModuleUpdated(old, new)`                   | `setComplianceModule`               |
| `MetadataURIUpdated(old, new)`                        | `setMetadataURI`                    |
| `SubIssuerUpdated(account, authorized)`               | `addSubIssuer`, `removeSubIssuer`   |
| `ControllerUpdated(account, authorized)`              | `addController`, `removeController` |
| `AuthorizedContractAdded(contractAddr)`               | `addAuthorizedContract`             |
| `AuthorizedContractRemoved(contractAddr)`             | `removeAuthorizedContract`          |
| `SubIssuerCapUpdated(subIssuer, oldCap, newCap)`      | `setSubIssuerCap`                   |

***

## IdentityRegistryUpgradeable

MiCA/eWpG-compliant KYC/AML registry shared across all token contracts in an instance.

### Initialization

```solidity theme={null}
function initialize(address gnosisSafe, address legalOperator) external initializer
```

The `gnosisSafe` address receives `DEFAULT_ADMIN_ROLE` and manages all role assignments. The `legalOperator` receives `LEGAL_OPERATOR_ROLE` for upgrade authorization.

### Roles

| Role                     | Purpose                                                         |
| ------------------------ | --------------------------------------------------------------- |
| `DEFAULT_ADMIN_ROLE`     | Role management, compliance module configuration, pause/unpause |
| `KYC_PROVIDER_ROLE`      | Verify, update identities and manage claims                     |
| `REGISTER_OPERATOR_ROLE` | Revoke identities                                               |
| `COMPLIANCE_ROLE`        | Freeze/unfreeze addresses                                       |
| `LEGAL_OPERATOR_ROLE`    | Authorize upgrades                                              |

### Identity Lifecycle

Identities move through a defined lifecycle: verification, optional updates, soft expiry (KYC refresh needed), hard expiry (transfers blocked), and revocation.

```solidity theme={null}
function verifyIdentity(
    address user,
    bytes32 kycHash,          // SHA256 of KYC data (zero-knowledge)
    InvestorType investorType, // RETAIL, PROFESSIONAL, or ELIGIBLE_COUNTERPARTY
    uint64 softExpiry,         // When KYC needs refresh
    uint64 hardExpiry          // When KYC becomes invalid for transfers
) external onlyRole(KYC_PROVIDER_ROLE)
```

Batch verification is available via `batchVerifyIdentities()` for up to 500 identities per call, with gas-aware early termination and per-entry validation. Failed entries are returned as an index array.

<Expandable title="KYCStatus enum">
  ```solidity theme={null}
  enum KYCStatus {
      ACTIVE,        // Valid, no issues
      SOFT_EXPIRED,  // Needs refresh, transfers still allowed
      HARD_EXPIRED,  // Transfers blocked until renewed
      REVOKED        // Permanently deactivated
  }
  ```

  Status is calculated dynamically from expiry timestamps, not stored. `SOFT_EXPIRED` does not block transfers - it signals that the KYC provider should schedule a refresh.
</Expandable>

### Claims

Claims are typed attestations attached to an identity. They support the full range of MiCA reporting requirements.

```solidity theme={null}
function addClaim(
    address user,
    ClaimType claimType, // KYC, AML, ACCREDITATION, RESIDENCY, CITIZENSHIP, TAX_RESIDENCY, SANCTIONS_CHECK, PEP_CHECK
    bytes32 dataHash,    // Zero-knowledge hash of claim data
    uint64 expiry        // 0 for no expiry
) external onlyRole(KYC_PROVIDER_ROLE)
```

Claims can be revoked individually via `revokeClaim()`. The `getClaims()` view returns only active claims.

### Transfer Compliance Check

```solidity theme={null}
function canTransfer(
    address from, address to, uint256 amount,
    uint256 fromBalance, uint256 toBalance
) external view returns (TransferCheckResult memory)
```

Returns a `TransferCheckResult` with `allowed`, `reason` (machine-readable `TransferRejectionReason`), and the KYC status of both parties. The check runs identity validation for both parties, then iterates all registered compliance modules. The registry fails closed on compliance module errors.

### Address Freezing

```solidity theme={null}
function freezeAddress(address user) external onlyRole(COMPLIANCE_ROLE)
function unfreezeAddress(address user) external onlyRole(COMPLIANCE_ROLE)
```

Freezing an address at the registry level blocks all transfers for that identity across all tokens using this registry. This is distinct from token-level freezing (which locks a specific amount on one token).

***

## BasicComplianceModule

Stateless compliance module that enforces holding limits and lockup periods. Deployed per token contract, controlled by the token contract (no separate admin keys).

### Constructor

```solidity theme={null}
constructor(
    address _tokenContract,     // Token that controls this module
    uint256 _defaultMaxHolding, // Global default max (0 = unlimited)
    uint256 _defaultMinHolding  // Global default min
)
```

### Rules

The module checks three conditions on every `canTransfer` call:

1. **Sender lockup.** If the sender's lockup has not expired, the transfer is rejected with `SENDER_LOCKED`.
2. **Sender minimum holding.** After the transfer, if the sender retains a non-zero balance below the minimum, the transfer is rejected with `BELOW_MIN_HOLDING`. Emptying an account entirely (balance going to zero) is always allowed.
3. **Recipient maximum holding.** If the recipient's balance after the transfer exceeds the maximum, the transfer is rejected with `RECIPIENT_EXCEEDS_MAX`.

Per-user overrides take precedence over defaults. Set per-user values to 0 to fall back to the global default.

### Configuration (called by token contract)

| Function                                           | Description                              |
| -------------------------------------------------- | ---------------------------------------- |
| `setDefaultLimits(uint256 max, uint256 min)`       | Update global default limits             |
| `setUserLimits(address, uint256 max, uint256 min)` | Override limits for a specific user      |
| `setLockup(address, uint256 until)`                | Set lockup expiry timestamp (0 to clear) |

### View Functions

| Function                                                | Returns                                                  |
| ------------------------------------------------------- | -------------------------------------------------------- |
| `getUserLimits(address)`                                | Effective max, effective min, and whether user is locked |
| `canTransfer(from, to, amount, fromBalance, toBalance)` | Compliance check result                                  |

<Expandable title="ComplianceRejectionReason enum">
  ```solidity theme={null}
  enum ComplianceRejectionReason {
      NONE,
      SENDER_LOCKED,
      BELOW_MIN_HOLDING,
      RECIPIENT_EXCEEDS_MAX,
      CUSTOM_RULE_VIOLATION
  }
  ```
</Expandable>
