> ## 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

> Technical reference for all smart contracts in the Commodity Token License suite.

This page documents every contract in the Commodity Token License. Each section covers the contract's purpose, key state variables, functions, and events. For deployment ordering and configuration, see [Integration](/licenses/commodities/integration).

## Factory

### CommodityTokenFactoryUpgradeable

Deploys commodity tokens as UUPS proxies sharing a common implementation contract. The factory is itself upgradeable (UUPS) and controlled by the TrussetDAO admin address (typically a Gnosis Safe).

**State**

| Variable                       | Type                       | Description                                        |
| ------------------------------ | -------------------------- | -------------------------------------------------- |
| `admin`                        | `address`                  | TrussetDAO multisig controlling the factory        |
| `commodityTokenImplementation` | `address`                  | Current default implementation for new deployments |
| `identityRegistry`             | `address`                  | Default KYC registry passed to new tokens          |
| `platformWallet`               | `address`                  | Platform fee wallet passed to new tokens           |
| `approvedImplementations`      | `mapping(address => bool)` | Implementations approved for token upgrades        |
| `allTokens`                    | `address[]`                | All deployed token proxy addresses                 |
| `isFactoryToken`               | `mapping(address => bool)` | Quick lookup for factory-deployed tokens           |

**Key Functions**

<Expandable title="createCommodityToken">
  ```solidity theme={null}
  function createCommodityToken(
      string memory name,
      string memory symbol,
      uint256 gramsPerToken,
      address mainIssuer,
      address redemptionAddress,
      uint256 maxSupply,
      bool kycRequired,
      bool useCustomRegistry,
      address customRegistry
  ) public onlyAdmin returns (address tokenAddress)
  ```

  Deploys a new `ERC1967Proxy` pointing to `commodityTokenImplementation` and calls `initialize` on the token. The `mainIssuer` receives full control over the token's operational functions. Set `maxSupply` to `0` for unlimited supply. If `kycRequired` is true and `useCustomRegistry` is false, the factory's default `identityRegistry` is used.

  A simplified variant `createSimpleCommodityToken` omits the custom registry parameters and always uses the factory default.
</Expandable>

<Expandable title="proposeUpgradeToTokens">
  ```solidity theme={null}
  function proposeUpgradeToTokens(
      address[] calldata tokenAddresses,
      address newImplementation
  ) external onlyAdmin
  ```

  Batch-proposes an upgrade to multiple factory-deployed tokens. Each token's issuer must individually accept the upgrade before it can execute. The implementation is auto-approved if not already in `approvedImplementations`.
</Expandable>

<Expandable title="setImplementation / setImplementationApproval">
  `setImplementation` updates the default implementation for future deployments and auto-approves it. `setImplementationApproval` approves or revokes a specific implementation address for token upgrades without changing the default.
</Expandable>

**Events:** `CommodityTokenCreated`, `ImplementationUpdated`, `ImplementationApproved`, `AdminUpdated`, `PlatformWalletUpdated`, `IdentityRegistryUpdated`

***

## Token

### CommodityTokenUpgradeable

The core ERC-20 token contract. Each instance represents a single commodity-backed token with its own reserve state, issuer hierarchy, fee configuration, and compliance settings. Deployed as a UUPS proxy via the factory.

Inherits: `Initializable`, `UUPSUpgradeable`, `EIP712Upgradeable`, `ReentrancyGuardUpgradeable`

### Access Control

The token separates authority into five distinct roles:

* **Main Issuer** - full control over all token operations, fee configuration, compliance settings, and issuer role transfer. Set at initialization, transferable via two-step process (`transferMainIssuer` then `acceptMainIssuer`). Only the pending address can complete the transfer, preventing accidental loss.
* **Delegates** - can perform most issuer operations except transferring the main issuer role, managing the delegate list, setting the upgrade authority, or accepting/rejecting upgrades. Managed via `setDelegate` / `setDelegatesBatch`.
* **Sub-Issuers** - configured via `configureIssuer` with granular `isMinter` / `isBurner` flags and per-address `mintLimit` caps.
* **Upgrade Authority** - the TrussetDAO multisig that proposes implementation upgrades and is the sole address permitted to rotate the `platformWallet` via `updatePlatformWallet`. Set at initialization to the factory `admin`, rotatable by the main issuer via `setUpgradeAuthority`.
* **Keeper Relayer** - a designated off-chain automation target for executing approved mint and burn requests. Set via `setKeeperRelayer` by the main issuer or a delegate. The address is configuration only and receives no implicit on-chain privileges, allowing integrators to gate keeper-specific workflows off-chain while keeping an auditable on-chain reference.

### Reserve Enforcement

Every token is backed by `gramsPerToken` grams of the underlying commodity (scaled to 18 decimals). The contract enforces this invariant:

```
totalSupply * gramsPerToken <= totalGramsReserve * 1e18
```

This check runs on every mint. `totalGramsReserve` increases when mint requests specify `gramsAdded`, and decreases on physical redemption. Reserve metadata (`vaultLocationHash`, `reserveAttestationURI`) is updatable by the issuer for off-chain audit linkage.

### Minting

Minting uses a request system. The caller creates a `MintRequest` specifying the recipient, amount, grams added to reserve, and one of four request types:

| Type              | Behavior                                                       |
| ----------------- | -------------------------------------------------------------- |
| `INSTANT`         | Auto-approved and executed in the same transaction             |
| `MANUAL_APPROVAL` | Requires explicit `approveMintRequest` call before execution   |
| `TIMELOCK`        | Executable only after `timelockSeconds` have elapsed           |
| `CONDITIONAL`     | Requires an EIP-712 signature from the main issuer or delegate |

Sub-issuers creating mint requests are checked against their `mintLimit`. The `gramsAdded` parameter must cover `(amount * gramsPerToken) / 1e18` or the transaction reverts with `InsufficientGramsAdded`.

`mintBatch` allows minting to multiple recipients in a single transaction with proportional grams distribution. The last recipient in the batch receives any rounding remainder.

### Redemption

Redemption also uses the request system. A holder calls `createBurnRequest` which locks the specified tokens (they remain in `_balances` but are tracked in `lockedBalances` and excluded from transferable balance).

**Physical redemption** (`burnTokens = true`): tokens are burned, `totalSupply` and `totalGramsReserve` decrease. If `unitsEnabled` is true, the redeemed grams must be divisible by `unitWeightGrams` (e.g., 1000 for 1kg bars). A `minTokensForAsset` threshold can enforce minimum redemption sizes.

**Cash redemption** (`burnTokens = false`): tokens are burned, the contract pays the holder in USDC at the current oracle price. The oracle must implement `IPriceOracle.getPrice()` returning `(price, decimals, timestamp)`. The contract normalizes from 18-decimal token amounts to USDC decimals:

```
usdcAmount = (tokenAmount * price) / 10^(18 + priceDecimals - usdcDecimals)
```

Oracle staleness is enforced via `maxOracleAge` (default 3600 seconds). The USDC must be pre-funded in the token contract.

### Fees

Mint and burn fees are configured independently in basis points (max 1000 = 10%). Fees are split 75% to the `mainIssuer` and 25% to the `platformWallet` (Trusset treasury). The platform wallet is updatable only by the `upgradeAuthority` (TrussetDAO), not by the issuer.

### Transfer Compliance

Transfers are checked against two independent layers:

1. **Identity Registry** (if `kycRequired` is true and `identityRegistry` is set): calls `IIdentityRegistry.canTransfer()` which validates identity status, expiry, freeze state, and runs all attached compliance modules. The full `TransferCheckResult` includes sender/receiver KYC status and machine-readable rejection reasons.

2. **Whitelist/Blacklist** (issuer-controlled via `transferRestrictionMode`):
   * `NONE` - no restrictions
   * `WHITELIST` - only whitelisted recipients can receive tokens
   * `BLACKLIST` - blacklisted addresses cannot send or receive

Both layers support batch operations (`setWhitelistBatch`, `setBlacklistBatch`).

### Upgrade Governance

Token upgrades follow a propose-accept pattern:

<Steps>
  <Step title="DAO proposes">
    The `upgradeAuthority` (TrussetDAO) calls `proposeUpgrade(newImplementation)`. This sets `pendingImplementation` and resets `upgradeAccepted` to false.
  </Step>

  <Step title="Issuer decides">
    The `mainIssuer` calls `acceptUpgrade()` or `rejectUpgrade()`. Rejection clears the pending implementation.
  </Step>

  <Step title="Upgrade executes">
    After acceptance, the issuer calls `upgradeToAndCall` (inherited from UUPS). The `_authorizeUpgrade` function verifies both proposal and acceptance before allowing the proxy to point to the new implementation.
  </Step>
</Steps>

Neither the DAO nor the issuer can upgrade unilaterally.

### Administrative Functions

`pause` / `unpause` halt all transfers, mints, and burns. `freezeAccount` / `unfreezeAccount` target individual addresses. `setKYCConfig` toggles KYC enforcement and updates the registry address. All administrative functions are callable by the main issuer or delegates.

**Events:** `Transfer`, `Approval`, `Minted`, `Redeemed`, `ReserveUpdated`, `Paused`, `Unpaused`, `AccountFrozen`, `AccountUnfrozen`, `MintRequestCreated`, `MintRequestApproved`, `MintRequestExecuted`, `BurnRequestCreated`, `BurnRequestApproved`, `BurnRequestExecuted`, `RequestCancelled`, `FeesCollected`, `CashRedemption`, `CashRedemptionOracleUpdated`, `UpgradeProposed`, `UpgradeAccepted`, `UpgradeRejected`, `UpgradeAuthorityUpdated`, `FeesUpdated`, `MainIssuerTransferProposed`, `MainIssuerTransferred`, `PlatformWalletUpdated`, `DelegateUpdated`, `KeeperRelayerUpdated`, `WhitelistUpdated`, `BlacklistUpdated`, `TransferRestrictionModeUpdated`

***

## Identity Registry

### IdentityRegistryUpgradeable

MiCA/eWpG-compliant identity management for regulated token transfers. Manages investor verification, claims, and transfer compliance with role-based access designed for German regulatory requirements.

**Roles**

| Role                     | Purpose                                            | Typical Holder                         |
| ------------------------ | -------------------------------------------------- | -------------------------------------- |
| `DEFAULT_ADMIN_ROLE`     | Manages all other roles, compliance modules, pause | Gnosis Safe multisig                   |
| `LEGAL_OPERATOR_ROLE`    | Authorizes contract upgrades                       | Named legal entity (BaFin requirement) |
| `KYC_PROVIDER_ROLE`      | Verifies/updates identities, manages claims        | Licensed KYC service                   |
| `REGISTER_OPERATOR_ROLE` | Revokes identities                                 | Security register operator             |
| `COMPLIANCE_ROLE`        | Freezes/unfreezes addresses                        | Compliance officer                     |

**Identity Lifecycle**

Each identity stores a `kycHash` (SHA-256 of off-chain KYC data), an `InvestorType` (MiFID II classification: `RETAIL`, `PROFESSIONAL`, or `ELIGIBLE_COUNTERPARTY`), the verifying provider, and two expiry timestamps:

* `softExpiry` - KYC needs refresh but transfers still allowed
* `hardExpiry` - KYC is invalid, transfers blocked

KYC status transitions: `ACTIVE` -> `SOFT_EXPIRED` -> `HARD_EXPIRED`. Revocation sets status to `REVOKED` permanently.

**Claims**

Claims are typed attestations attached to an identity. Supported types: `KYC`, `AML`, `ACCREDITATION`, `RESIDENCY`, `CITIZENSHIP`, `TAX_RESIDENCY`, `SANCTIONS_CHECK`, `PEP_CHECK`. Each claim stores a zero-knowledge `dataHash`, the issuing provider, and an optional expiry.

**Transfer Compliance**

`canTransfer` validates both sender and receiver against identity status, expiry, freeze state, and all registered compliance modules. It returns a `TransferCheckResult` with:

* `allowed` - boolean
* `reason` - machine-readable `TransferRejectionReason` enum
* `senderStatus` / `receiverStatus` - current `KYCStatus` for both parties

Compliance modules are called in sequence. If any module rejects or throws, the transfer is blocked (fail-closed).

**Batch Operations**

`batchVerifyIdentities` processes up to 500 identities per call with gas-aware iteration - the loop breaks if remaining gas drops below 50,000 per item. Returns `successCount` and `failedIndices` for partial-success handling. `batchRevokeIdentities` supports up to 1,000 revocations per call.

***

## Compliance Module

### BasicComplianceModule

A stateless compliance module enforcing holding limits and lockup periods. Deployed per token contract - the token contract is the sole authorized caller.

**Rules**

* **Maximum holding**: prevents a recipient's post-transfer balance from exceeding the cap. Per-user overrides fall back to `defaultMaxHolding`.
* **Minimum holding**: prevents a sender from retaining a balance below the floor (unless they empty the account entirely). Per-user overrides fall back to `defaultMinHolding`.
* **Lockup**: blocks all outbound transfers from an address until the lockup timestamp expires.

The module implements `IComplianceModule.canTransfer` and returns machine-readable `ComplianceRejectionReason` values: `SENDER_LOCKED`, `BELOW_MIN_HOLDING`, `RECIPIENT_EXCEEDS_MAX`, or `NONE`.

`getUserLimits` returns the effective max, effective min, and lock status for any address.

***

## Sale

### CommodityTokenSale

Primary market sale contract supporting fixed-price and oracle-priced commodity token sales. Each sale contract is bound to a single commodity token and can host multiple sale rounds identified by `bytes32` sale IDs.

**Sale Configuration**

Each sale round specifies:

| Field                         | Description                                                                             |
| ----------------------------- | --------------------------------------------------------------------------------------- |
| `mintOnPurchase`              | If true, calls `createMintRequest` on the token. If false, transfers pre-funded tokens. |
| `fixedPriceUsd`               | Fixed USD price per token (18 decimals). Set `0` to use oracle.                         |
| `priceOracle`                 | `IPriceOracle` address for dynamic pricing                                              |
| `paymentToken`                | ERC-20 address for payment, or `address(0)` for native ETH                              |
| `minPurchase` / `maxPurchase` | Per-purchase minimum and per-buyer maximum (in tokens)                                  |
| `totalAllocation`             | Total tokens available in this round                                                    |
| `feeBps`                      | Sale fee in basis points (max 500 = 5%), split 75/25 issuer/platform                    |
| `startTime` / `endTime`       | Sale window (0 = no constraint)                                                         |
| `mintRequestType`             | Request type passed to `createMintRequest` (0-3)                                        |

**Purchase Flow**

Buyers call `purchase(saleId, tokenAmount, maxPayment)` where `maxPayment` provides slippage protection. The contract calculates the payment amount from the configured price source, adds the fee, and verifies it does not exceed `maxPayment`. For ETH payments, excess `msg.value` is refunded.

For `mintOnPurchase` sales, the sale contract must be configured as an authorized minter on the commodity token (via `configureIssuer`). The `gramsNeeded` for the mint request is computed from the token's `gramsPerToken`.

**Pre-funded sales** require the issuer to call `fundSale` to transfer tokens into the sale contract before purchases begin. Unsold tokens can be recovered via `withdrawUnsoldTokens` (deactivates the sale).

***

## Sale Factory

### CommodityTokenSaleFactory

Deploys `CommodityTokenSale` instances. Admin-gated - only the factory admin can create sale contracts. Tracks deployments per issuer and per commodity token, supporting multiple sale rounds per token.

**Key Functions**

* `createSaleContract(commodityToken, issuer)` - deploys a new sale contract with the factory's `platformWallet`
* `getTokenSaleContracts(commodityToken)` - returns all sale contracts for a specific token
* `getIssuerSaleContracts(issuer)` - returns all sale contracts for an issuer
* `getSaleContractsPaginated(offset, limit)` - paginated listing of all deployments

***

## Interfaces

The suite includes four interfaces used for cross-contract communication:

* `ICommodityToken` - minimal interface for sale contracts to call `createMintRequest`, `transfer`, `gramsPerToken`, and other token functions
* `IIdentityRegistry` - full identity registry interface including all enums (`InvestorType`, `KYCStatus`, `ClaimType`, `TransferRejectionReason`), structs (`Claim`, `TransferCheckResult`), and function signatures
* `IComplianceModule` - single `canTransfer` function with `ComplianceRejectionReason` return
* `IPriceOracle` - `getPrice()` returning `(price, decimals, timestamp)` for USD-denominated price feeds
