> For the complete documentation index, see [llms.txt](https://docs.trilobyte.finance/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.trilobyte.finance/technical-documentation/collateral-escrow-contract.md).

# Collateral Escrow Contract

A generic escrow that custodies **borrower-posted collateral** against a loan. It is one of Trilobyte's two collateral systems — the other (pool-manager skin-in-the-game collateral) lives in [Globals](/technical-documentation/globals-contract.md).

**Source:** `contracts/collateral-escrow/`

## Overview

The escrow custodies pledged collateral of **any** asset that implements the token interface — fungible tokens, RWA certificates via the Stellar Asset Contract, or NFTs. Its only job is `lock` / `release` / `seize`. Valuation, LTV, and what "seize" means by collateral type are policy that lives in configuration around the escrow, not in the contract.

**Vault-triggered.** Each pledge names its own `controller` — the Trilobyte vault the loan funds against — and **only that controller may release or seize it**. A single shared escrow can therefore back many loans and be authorised once by a permissioned-cert issuer.

{% hint style="info" %}
The minimal escrow holds **at most one active pledge per token**. With one pledge per token, the escrow's balance of that token equals exactly that pledge's collateral, which removes cross-pledge balance ambiguity (e.g. after an issuer clawback). Resolved pledges are **pruned** — their final state lives in the emitted event.
{% endhint %}

## Source Modules

| File          | Purpose                                                                               |
| ------------- | ------------------------------------------------------------------------------------- |
| `contract.rs` | `__constructor`, `lock`, `release`, `seize`, queries, the `available()` clamp helper  |
| `storage.rs`  | `Pledge` struct, pledge counter, per-id pledge records, active-pledge-per-token index |
| `errors.rs`   | `#[contracterror]` enum (3 variants)                                                  |
| `events.rs`   | `PledgeLocked`, `PledgeReleased`, `PledgeSeized`                                      |
| `test.rs`     | 11 tests                                                                              |

## Data Model

### Pledge

```rust
Pledge {
    borrower: Address,        // Who pledged the collateral
    token: Address,           // The pledged asset
    amount: i128,             // Recorded pledged amount
    recovery_party: Address,  // Receives the asset on seize (default)
    controller: Address,      // The vault allowed to release/seize this pledge
}
```

The record exists only while the pledge is active; it is deleted on `release` / `seize`.

## Functions

| Function               | Signature                                                          | Auth                | Description                                                                                                                                                                                                                                       |
| ---------------------- | ------------------------------------------------------------------ | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `lock`                 | `lock(borrower, token, amount, recovery_party, controller) -> u64` | `borrower`          | Borrower pledges `amount` of `token`, naming the `controller` vault and the `recovery_party`. Pulls the collateral into the escrow and returns the pledge id. Fails with `TokenAlreadyPledged` if an active pledge for that token already exists. |
| `release`              | `release(pledge_id)`                                               | pledge `controller` | Return the collateral to the borrower (loan repaid). Prunes the pledge first (effects-before-interaction), then transfers the available amount.                                                                                                   |
| `seize`                | `seize(pledge_id)`                                                 | pledge `controller` | Send the collateral to the `recovery_party` (loan defaulted). Prunes the pledge first, then transfers the available amount.                                                                                                                       |
| `get_pledge`           | `get_pledge(pledge_id) -> Option<Pledge>`                          | —                   | The active pledge record, or `None` once resolved.                                                                                                                                                                                                |
| `get_active_pledge`    | `get_active_pledge(token) -> Option<u64>`                          | —                   | The active pledge id for a token (lets a controller find a pledge without having stored the id).                                                                                                                                                  |
| `available_collateral` | `available_collateral(pledge_id) -> i128`                          | —                   | Currently transferable amount: the recorded amount clamped to the escrow's live balance. Returns 0 if the pledge does not exist.                                                                                                                  |
| `__constructor`        | `__constructor()`                                                  | —                   | Deploy the shared escrow. No global authority — each pledge carries its own controller.                                                                                                                                                           |

{% hint style="warning" %}
`available_collateral` exists because a permissioned RWA certificate can be **clawed back by its issuer while escrowed**. A controller should gate lending on the live `available_collateral`, not the recorded `amount`. The vault's disbursement gate does exactly this. If `seize` finds `amount == 0` (fully clawed back), the pledge is still closed and the event records `amount = 0`.
{% endhint %}

## How the Vault Uses It

1. **Setup** — the loan is created with `collateral_escrow`, `collateral_token`, `collateral_recovery`, and `collateral_min` set on `VaultParams`.
2. **Lock** — the borrower calls `lock(...)` naming the vault as `controller`, and passes the returned `pledge_id` to `approve_and_disburse(caller, Some(pledge_id))`.
3. **Disbursement gate** — the vault verifies the pledge backs *this* vault: `controller == vault`, `token == collateral_token`, `borrower == loan borrower`, `recovery_party == collateral_recovery`, and both `pledge.amount` and the **live** `available_collateral` are ≥ `collateral_min`. Any mismatch → `CollateralPledgeInvalid`.
4. **Repaid** — on `FullyRepaid` the vault calls `escrow.release(pledge_id)` → collateral back to the borrower.
5. **Defaulted** — on `check_default` the vault calls `escrow.seize(pledge_id)` → collateral to the `recovery_party` for off-chain disposition.
6. **Cancelled before disbursement** — on funding/approval expiry the vault looks the pledge up by token (the id was never recorded), re-verifies controller + borrower, and releases it back to the borrower.

## Events

| Event            | Key Fields                                               | When                         |
| ---------------- | -------------------------------------------------------- | ---------------------------- |
| `PledgeLocked`   | `borrower`, `pledge_id`, `token`, `amount`, `controller` | Collateral locked            |
| `PledgeReleased` | `pledge_id`, `amount`                                    | Released to the borrower     |
| `PledgeSeized`   | `recovery_party`, `pledge_id`, `amount`                  | Seized to the recovery party |

## Error Codes

| Code | Name                  | Description                                                        |
| ---- | --------------------- | ------------------------------------------------------------------ |
| 1    | `PledgeNotFound`      | No active pledge with that id (never created, or already resolved) |
| 2    | `InvalidAmount`       | The pledged amount must be positive                                |
| 3    | `TokenAlreadyPledged` | This token already has an active pledge — resolve it first         |

## Storage Design

The pledge counter, per-id pledge records, and the active-pledge-per-token index are all in **persistent storage** with a TTL clamped to the network maximum (`max_ttl()`). Reads (`get_pledge`) do not bump TTL — reads must not side-effect storage.
