> ## 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, state management, access control, and function reference for CommodityCustody.

The Commodity Orderbook suite consists of a single deployable contract (`CommodityCustody`) with external dependencies on commodity tokens, USDC, a `LiquidationRouter`, and optionally a lending market. This page covers the contract's architecture, state layout, and complete function reference.

## Architecture

`CommodityCustody` maintains an internal ledger of user balances using a `keccak256(user, token)` key scheme. Deposits move tokens from the user into the contract and credit the internal balance. Withdrawals debit the internal balance and transfer tokens out. Trade settlements move balances between users internally without any on-chain token transfer.

All tokens - commodity tokens and USDC alike - are registered and treated uniformly via `addSupportedToken`. There is no compliance classification flag. Commodity tokens enforce their own compliance (KYC, whitelist/blacklist, frozen accounts, pause state) inside their `transfer()` and `transferFrom()` implementations. If the custody contract or user fails a compliance check, the commodity token reverts the transfer directly.

```
                      +--------------------+
                      |  CommodityCustody  |
                      |  (this contract)   |
                      +--------+-----------+
                               |
          +--------------------+--------------------+
          |                    |                    |
+---------v----------+ +------v--------+  +--------v---------+
| CommodityToken     | |    USDC       |  | LiquidationRouter|
| (self-enforcing    | |   (IERC20)    |  |   (external)     |
|  compliance)       | +---------------+  +--------+---------+
+---------+----------+                             |
          |                                +-------v---------+
+---------v----------+                     |  LendingMarket  |
| IdentityRegistry   |                     | (ILendingMarket)|
|   (indirect)       |                     +-----------------+
+--------------------+
```

The IdentityRegistry is never called directly by CommodityCustody. It is referenced indirectly through the commodity token's transfer validation when KYC is required on that token.

### Compliance Model Difference from StockCustody

[StockCustody](/licenses/stock-orderbook/contracts) performs pre-transfer compliance checks via `ISecurityToken.canTransfer()` and classifies tokens as compliance-checked or standard. CommodityCustody does not - commodity tokens enforce compliance inside their own `transfer()` and `transferFrom()` implementations. The custody contract has no `complianceChecked` flag and no `checkTransferability` function. If a compliance check fails, the commodity token reverts the transfer with its own error codes, not CommodityCustody errors.

## CommodityCustody

UUPS-upgradeable custody contract with internal ledger, trade settlement, liquidation handling, and emergency controls.

### Initialization

```solidity theme={null}
function initialize(
    address _admin,              // Contract administrator (cannot be zero)
    address _operator,           // Settlement operator (zero defaults to admin)
    address _liquidationRouter,  // Liquidation router address (cannot be zero)
    address _usdc                // USDC token address (cannot be zero)
) external initializer
```

The initializer sets up the admin, operator, liquidation router, and USDC address. If `_operator` is `address(0)`, it defaults to the admin address.

### State Variables

| Variable                  | Type                                  | Description                            |
| ------------------------- | ------------------------------------- | -------------------------------------- |
| `admin`                   | `address`                             | Contract administrator                 |
| `pendingAdmin`            | `address`                             | Pending admin during two-step transfer |
| `settlementOperator`      | `address`                             | Operator authorized for settlements    |
| `liquidationRouter`       | `address`                             | External router for liquidation flows  |
| `usdc`                    | `address`                             | USDC settlement token address          |
| `withdrawalsPaused`       | `bool`                                | Global withdrawal pause flag           |
| `pendingLiquidationCount` | `uint256`                             | Count of unsettled liquidations        |
| `_balances`               | `mapping(bytes32 => uint256)`         | Total balance per user-token pair      |
| `_lockedBalances`         | `mapping(bytes32 => uint256)`         | Locked balance per user-token pair     |
| `_processedSettlements`   | `mapping(bytes32 => bool)`            | Deduplication for settlement IDs       |
| `_supportedTokens`        | `mapping(address => bool)`            | Registered token whitelist             |
| `_frozenUsers`            | `mapping(address => bool)`            | Per-user withdrawal freeze             |
| `liquidations`            | `mapping(uint256 => LiquidationInfo)` | Liquidation records                    |

### Access Control

<Expandable title="Modifier definitions">
  | Modifier                | Allows                             |
  | ----------------------- | ---------------------------------- |
  | `onlyAdmin`             | `admin` only                       |
  | `onlyOperator`          | `settlementOperator` or `admin`    |
  | `onlyLiquidationRouter` | `liquidationRouter` only           |
  | `tokenSupported(token)` | Reverts if token is not registered |
</Expandable>

### Admin Management

Admin transfer is a two-step process to prevent accidental transfer to an incorrect address.

```solidity theme={null}
function transferAdmin(address newAdmin) external onlyAdmin
function acceptAdmin() external  // only callable by pendingAdmin
```

```solidity theme={null}
function updateOperator(address newOperator) external onlyAdmin
```

### Token Configuration

```solidity theme={null}
function addSupportedToken(address token) external onlyAdmin
function removeSupportedToken(address token) external onlyAdmin
```

Both commodity tokens and settlement tokens (USDC) are registered the same way. There is no compliance classification - commodity tokens enforce their own compliance in `transfer()` and `transferFrom()`.

Removing a token from the supported list does not lock user funds. Users can still withdraw unsupported tokens. New deposits and trade settlements for the removed token are blocked.

### Deposit and Withdrawal

```solidity theme={null}
function deposit(address token, uint256 amount) external nonReentrant tokenSupported(token)
```

Transfers tokens from the caller into custody via `transferFrom`. For commodity tokens, the token itself enforces KYC, whitelist/blacklist, frozen, and pause checks during the transfer. If any check fails, the commodity token reverts with its own error codes. The caller must have approved the custody contract for the deposit amount.

```solidity theme={null}
function withdraw(address token, uint256 amount) external nonReentrant
```

Withdraws available (unlocked) tokens from custody via `transfer`. The commodity token enforces compliance during the transfer. Blocked if the user is frozen in custody or withdrawals are globally paused. Allows withdrawal of unsupported tokens to prevent fund lockout after token removal.

<Warning>
  There is no `checkTransferability` pre-check function on CommodityCustody. Commodity token compliance failures surface as reverts from the token contract itself during `deposit` or `withdraw`. Integrate with the commodity token's compliance view functions directly (e.g., check whitelist status, KYC verification, frozen state) before submitting deposit or withdrawal transactions.
</Warning>

### Balance Locking

The operator locks user balances when orders are matched, before settlement.

```solidity theme={null}
function lockBalance(address user, address token, uint256 amount, bytes32 lockId)
    external onlyOperator tokenSupported(token)
```

Locks `amount` from the user's available balance (total minus already locked). Reverts with `InsufficientBalance` if available balance is insufficient.

```solidity theme={null}
function unlockBalance(address user, address token, uint256 amount, bytes32 lockId)
    external onlyOperator tokenSupported(token)
```

Unlocks previously locked tokens. Reverts with `InsufficientLockedBalance` if the locked balance is less than `amount`.

### Trade Settlement

```solidity theme={null}
function settleTrade(
    bytes32 settlementId,
    address maker, address taker,
    address baseToken, address quoteToken,
    uint256 baseAmount, uint256 quoteAmount,
    bool makerIsBuyer
) external onlyOperator nonReentrant
```

Atomically transfers locked base tokens from seller to buyer and locked quote tokens from buyer to seller. These are internal ledger movements only - no on-chain token transfers occur. Both parties must have sufficient locked balances. The `settlementId` must be unique - reuse reverts with `SettlementAlreadyProcessed`.

```solidity theme={null}
function batchSettleTrades(
    bytes32 batchId,
    bytes32[] calldata settlementIds,
    address[] calldata makers,
    address[] calldata takers,
    address[] calldata baseTokens,
    address[] calldata quoteTokens,
    uint256[] calldata baseAmounts,
    uint256[] calldata quoteAmounts,
    bool[] calldata makerIsBuyers
) external onlyOperator nonReentrant
```

Settles multiple trades in one transaction. Skips already-processed settlement IDs. Includes a gas guard that stops processing if remaining gas drops below 80,000 per iteration. Emits `BatchSettled(batchId, settledCount, totalSubmitted)` so callers can detect partial completion.

<Warning>
  The `batchSettleTrades` function takes 9 calldata array parameters, which exceeds the legacy compilation pipeline's stack limit. The Solidity compiler must use `viaIR: true`. See [Integration](/licenses/commodity-orderbook/integration) for compiler configuration.
</Warning>

```solidity theme={null}
function settlePriorityTrade(
    bytes32 settlementId,
    address liquidatedUser, address buyer,
    address baseToken, address quoteToken,
    uint256 baseAmount, uint256 quoteAmount
) external onlyOperator nonReentrant
```

Priority settlement for liquidated positions. The liquidated user's tokens must be locked first. Use `freezeUser()` before locking to prevent withdrawal front-running between the lock and settlement steps. Emits both `PrioritySettlement` and `TradeSettled`.

### Liquidation

#### Receiving Collateral

```solidity theme={null}
function receiveLiquidatedCollateral(
    address token, uint256 amount, uint256 debtOwed,
    address borrower, address lendingMarket, uint256 liquidationId
) external onlyLiquidationRouter nonReentrant
```

Called by the liquidation router to deliver seized collateral into custody. Tokens are pulled from the router (which must have approved the custody contract) and credited to an internal liquidation pool keyed to `address(this)`.

#### External Sale Settlement

```solidity theme={null}
function settleLiquidation(
    uint256 liquidationId, uint256 usdcReceived, address tokenRecipient
) external onlyOperator nonReentrant
```

Settles after the operator sells tokens through an external venue. Commodity tokens transfer to `tokenRecipient` - the commodity token enforces its own compliance during this transfer. USDC is pulled from the operator and sent to the liquidation router. The lending market receives a `receiveLiquidationProceeds` callback. If the callback reverts, settlement still completes.

#### Internal Buyer Settlement

```solidity theme={null}
function settleLiquidationWithBuyer(
    uint256 liquidationId, address buyer,
    uint256 tokenAmount, uint256 usdcAmount
) external onlyOperator nonReentrant
```

Matches liquidation tokens with a buyer who has USDC in custody. Tokens move from the liquidation pool to the buyer's custody balance (internal ledger operation, no on-chain token transfer). USDC is deducted from the buyer's internal balance and transferred on-chain to the router. Supports partial fills - the liquidation remains open until the full amount is matched. The lending market is notified via callback.

### Emergency Controls

```solidity theme={null}
function freezeUser(address user) external onlyOperator
function unfreezeUser(address user) external onlyOperator
```

Per-user withdrawal freeze at the custody level. Used to prevent front-running during liquidation or for compliance holds. Does not affect the user's ability to have their balances settled by the operator. Independent of the commodity token's own freeze mechanism.

```solidity theme={null}
function pauseWithdrawals() external onlyAdmin
function unpauseWithdrawals() external onlyAdmin
```

Global withdrawal circuit breaker. Blocks all withdrawals for all users.

### View Functions

| Function                              | Returns                                                                                        |
| ------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `getBalance(user, token)`             | Total balance (locked + available)                                                             |
| `getLockedBalance(user, token)`       | Locked balance                                                                                 |
| `getAvailableBalance(user, token)`    | Available balance (total minus locked)                                                         |
| `isSettlementProcessed(settlementId)` | Whether a settlement ID has been used                                                          |
| `isTokenSupported(token)`             | Whether a token is registered                                                                  |
| `isUserFrozen(user)`                  | Whether a user's withdrawals are frozen                                                        |
| `getLiquidation(liquidationId)`       | Full liquidation record (token, amount, debtOwed, borrower, lendingMarket, timestamp, settled) |

### Events

| Event                      | Parameters                                                                 | When Emitted                       |
| -------------------------- | -------------------------------------------------------------------------- | ---------------------------------- |
| `Deposited`                | user, token, amount, newBalance                                            | User deposits tokens               |
| `Withdrawn`                | user, token, amount                                                        | User withdraws tokens              |
| `BalanceLocked`            | user, token, amount, lockId                                                | Operator locks balance             |
| `BalanceUnlocked`          | user, token, amount, lockId                                                | Operator unlocks balance           |
| `TradeSettled`             | settlementId, maker, taker, baseToken, quoteToken, baseAmount, quoteAmount | Trade settlement completes         |
| `BatchSettled`             | batchId, settledCount, totalSubmitted                                      | Batch settlement completes         |
| `PrioritySettlement`       | settlementId, liquidatedUser, token                                        | Priority liquidation trade settled |
| `LiquidationReceived`      | liquidationId, token, amount, debtOwed, borrower, lendingMarket            | Collateral received from router    |
| `LiquidationSettled`       | liquidationId, usdcReceived, tokenRecipient                                | External liquidation sale settled  |
| `LiquidationSoldToBuyer`   | liquidationId, buyer, tokenAmount, usdcAmount, fullyFilled                 | Internal buyer fills liquidation   |
| `OperatorUpdated`          | oldOperator, newOperator                                                   | Operator address changed           |
| `TokenSupported`           | token, supported                                                           | Token added or removed             |
| `LiquidationRouterUpdated` | oldRouter, newRouter                                                       | Router address changed             |
| `UsdcAddressUpdated`       | oldUsdc, newUsdc                                                           | USDC address changed               |
| `AdminTransferInitiated`   | currentAdmin, pendingAdmin                                                 | Admin transfer started             |
| `AdminTransferCompleted`   | oldAdmin, newAdmin                                                         | Admin transfer accepted            |
| `UserFrozen`               | user, operator                                                             | User withdrawals frozen            |
| `UserUnfrozen`             | user, operator                                                             | User withdrawals unfrozen          |
| `WithdrawalsPaused`        | operator                                                                   | Global withdrawal pause            |
| `WithdrawalsUnpaused`      | operator                                                                   | Global withdrawal unpause          |

### Errors

| Error                        | Trigger                                                     |
| ---------------------------- | ----------------------------------------------------------- |
| `Unauthorized`               | Caller lacks required role                                  |
| `InsufficientBalance`        | Available balance less than requested amount                |
| `InsufficientLockedBalance`  | Locked balance less than transfer/unlock amount             |
| `ZeroAmount`                 | Amount parameter is 0                                       |
| `ZeroAddress`                | Address parameter is `address(0)`                           |
| `SettlementAlreadyProcessed` | Settlement ID reused                                        |
| `InvalidSettlement`          | Batch array lengths mismatch                                |
| `TransferFailed`             | ERC-20 transfer returned false                              |
| `TokenNotSupported`          | Token not registered via `addSupportedToken`                |
| `LiquidationNotFound`        | Liquidation ID does not exist                               |
| `LiquidationAlreadySettled`  | Liquidation already fully settled                           |
| `LiquidationAmountExceeded`  | Requested token amount exceeds remaining liquidation amount |
| `BuyerInsufficientUsdc`      | Buyer's USDC custody balance too low                        |
| `UserIsFrozen`               | Frozen user attempts withdrawal                             |
| `WithdrawalsArePaused`       | Withdrawal attempted during global pause                    |
| `InvalidBatchSize`           | Batch settlement called with empty array                    |
| `AdminTransferNotPending`    | `acceptAdmin` called by non-pending address                 |

<Info>
  Commodity token compliance failures (KYC, whitelist, blacklist, frozen, paused) cause the token's own `transfer` or `transferFrom` call to revert with the commodity token's error codes, not CommodityCustody errors. Your integration layer must handle both error sources when processing deposits and withdrawals.
</Info>

***

## ILendingMarket

Callback interface that lending market contracts must implement to receive liquidation settlement notifications.

```solidity theme={null}
interface ILendingMarket {
    function receiveLiquidationProceeds(uint256 liquidationId, uint256 amount) external;
}
```

Called by `CommodityCustody` when a liquidation is settled (both `settleLiquidation` and `settleLiquidationWithBuyer`). If the callback reverts, settlement still completes. Off-chain systems must reconcile via `LiquidationSettled` or `LiquidationSoldToBuyer` events.
