> 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/vault-contract.md).

# Vault Contract

The core lending contract. Each vault represents a single loan — created with negotiated terms, funded by investors, disbursed to the borrower, and repaid through incoming cash-flow payments.

**Source:** `contracts/vault/`

## Source Modules

| File          | Purpose                                                                                                                                     |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `contract.rs` | All public entry points, SEP-41 FungibleToken + FungibleBurnable impls, OZ Upgradeable, inline `GlobalsClient` and `CollateralEscrowClient` |
| `storage.rs`  | Data types, storage keys, getters/setters, phase transitions                                                                                |
| `math.rs`     | Fixed-point integer EMI calculation, split logic, gross-up, late fees                                                                       |
| `errors.rs`   | `#[contracterror]` enum (34 is the highest code)                                                                                            |
| `events.rs`   | `#[contractevent]` structs (20 events)                                                                                                      |
| `token.rs`    | Documentation module — explains the FungibleToken impl approach                                                                             |
| `test.rs`     | 64 contract tests (+ 19 math tests in `math.rs` = 83 total)                                                                                 |

## Data Model

### VaultConfig (21 fields)

```rust
VaultConfig {
    borrower: Address,                    // Business receiving the loan
    lent_token: Address,                  // Token being lent (e.g. USDC)
    manager: Address,                     // Pool Manager who underwrote this vault
    principal: i128,                      // Requested loan amount (7-decimal)
    interest_rate: u32,                   // Annual rate in basis points (1000 = 10%)
    loan_term: u32,                       // Term in months
    split_ratio: u32,                     // % of payments routed to EMI pool (1–99)
    permissioned: bool,                   // Whether only allowlisted investors can deposit
    grace_period: u64,                    // Seconds after due date before default
    phase: VaultPhase,                    // Current lifecycle phase
    created_at: u64,                      // Creation timestamp
    disbursed_at: u64,                    // Disbursement timestamp (0 if not yet)
    funding_deadline: u64,                // Deadline for full funding (0 = no deadline)
    approval_deadline: u64,               // Deadline for manager approval (0 = no deadline)
    collateral_amount: i128,              // Manager collateral snapshotted at creation
    collateral_escrow: Option<Address>,   // Borrower-collateral escrow (None = none)
    collateral_token: Option<Address>,    // Asset the borrower must pledge
    collateral_recovery: Option<Address>, // Receives the pledge on default
    collateral_min: i128,                 // On-chain floor the pledge must meet at disbursement
    pledge_id: u64,                       // Escrow pledge id, recorded at disbursement (0 until then)
}
```

{% hint style="info" %}
`collateral_amount` is the manager (skin-in-the-game) collateral, snapshotted at creation so release/slash always use a fixed amount rather than a later (mutable) global ratio. The `collateral_*` fields concern **borrower-posted** collateral held in the [CollateralEscrow](/technical-documentation/collateral-escrow-contract.md).
{% endhint %}

### VaultPhase

```rust
VaultPhase {
    RaisingFunds = 0,     // Open for investor deposits
    AwaitingApproval = 1, // Fully funded, awaiting manager approval & disbursement
    Active = 2,           // Loan disbursed, borrower is repaying
    FullyRepaid = 3,      // Debt cleared — investors can claim remaining yield
    Finalized = 4,        // Vault closed, all funds distributed
    Renegotiation = 5,    // Borrower proposed new terms — under review
    Defaulted = 6,        // Borrower failed to repay within grace period
}
```

### Lifecycle Diagram

```
RaisingFunds → AwaitingApproval → Active → FullyRepaid
     │                  │            ↕
     │ (funding         │ (approval  Renegotiation
     │  expiry)         │  expiry)
     ▼                  ▼            Active → Defaulted
  Finalized          Finalized
```

The valid transitions enforced by `transition_phase` are:

* `RaisingFunds → AwaitingApproval` (auto, on full funding)
* `AwaitingApproval → Active` (manager approves & disburses)
* `Active → FullyRepaid` (debt cleared)
* `Active → Renegotiation` and `Renegotiation → Active`
* `Active → Defaulted`
* `FullyRepaid → Finalized` (defined as valid, but see note)

{% hint style="warning" %}
**Renegotiation is only reachable from `Active`** — not from `Defaulted` (a defaulted loan has already had its manager collateral slashed, so re-activating would leave it unbacked).

**No function performs `FullyRepaid → Finalized`.** The transition is defined in `transition_phase` but never triggered; the only writes to `Finalized` are the funding- and approval-expiry paths (which set the phase directly: `RaisingFunds → Finalized` and `AwaitingApproval → Finalized`).
{% endhint %}

## Functions

### Constructor

```
__constructor(globals, params: VaultParams)
```

Deploy a new vault. Validates split ratio, principal, term, rate, and amount against Globals bounds; if `collateral_escrow` is set, asserts the other borrower-collateral fields are present. Sets debt-token metadata (SEP-41) and cap (= principal), and snapshots the manager collateral (`principal × default_collateral_ratio / 100`). The admin is set to the Globals admin (governor/timelock) for upgradeability.

### Investor Actions (RaisingFunds)

| Function                     | Description                                                                                                                                                                                                                               |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `deposit(investor, amount)`  | Deposit funds; deducts the protocol fee (sent to treasury), credits the net amount, mints debt tokens 1:1 with the net. For permissioned vaults the investor must be allowlisted. Auto-transitions to AwaitingApproval when fully funded. |
| `withdraw(investor, amount)` | Withdraw before fully funded; burns debt tokens.                                                                                                                                                                                          |

### Manager / Borrower Actions

| Function                                               | Description                                                                                                                                                                                                                                                                                                                                                                      |
| ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `approve_and_disburse(caller, pledge_id: Option<u64>)` | Manager approves the loan and disburses to the borrower; calculates EMI and initialises the schedule. AwaitingApproval → Active. **Borrower-collateral gate:** if the vault has a `collateral_escrow`, `pledge_id` is required and the escrow pledge is verified against this vault (controller, token, borrower, recovery party, and `amount`/live balance ≥ `collateral_min`). |
| `add_to_allowlist(caller, investor)`                   | Add an investor to a permissioned vault's allowlist (manager only).                                                                                                                                                                                                                                                                                                              |
| `remove_from_allowlist(caller, investor)`              | Remove an investor from the allowlist (manager only).                                                                                                                                                                                                                                                                                                                            |

### Payment Actions (Active)

| Function                         | Description                                                                                                                                                                                                                                       |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `receive_payment(payer, amount)` | Accept a **gross** payment (see math below). Splits gross → EMI pool + cash pool, takes the protocol fee from the cash share only, amortises outstanding on the EMI-pool principal component. Transitions to FullyRepaid when `outstanding == 0`. |
| `claim_yield(investor)`          | Claim pro-rata share of the EMI pool (Active or FullyRepaid).                                                                                                                                                                                     |
| `withdraw_cash(amount)`          | Borrower withdraws from the cash pool (Active or FullyRepaid).                                                                                                                                                                                    |

### Deadline Enforcement (Permissionless)

| Function                  | Description                                                                                                                                                           |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `check_funding_expiry()`  | Cancel if the funding deadline passed: refund investors, release manager collateral, decrement outstanding, refund any borrower collateral. RaisingFunds → Finalized. |
| `check_approval_expiry()` | Cancel if the approval deadline passed: same teardown. AwaitingApproval → Finalized.                                                                                  |

### Risk & Default (Active)

| Function                 | Description                                                                                                                                                                                                                           |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `apply_late_fee(caller)` | **Manager only.** Applies a late fee when overdue. The penalty rate is **hardcoded at 1800 bps (18% p.a.)**, not read from settings. Increments the missed-payment counter and advances `next_due`.                                   |
| `check_default()`        | Anyone can call. If past the contractual due date + grace period: slash manager collateral to the vault, seize any borrower collateral to the recovery party, mark the manager delinquent, decrement outstanding. Active → Defaulted. |

### Renegotiation (Active only)

| Function                                    | Description                                                                                                            |
| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `propose_renegotiation(caller, rate, term)` | Manager proposes new terms (from Active only). Saves the previous phase. Active → Renegotiation.                       |
| `approve_renegotiation(caller)`             | Apply new terms, recalculate EMI on the **current outstanding** principal, reset the schedule. Renegotiation → Active. |
| `reject_renegotiation(caller)`              | Cancel the proposal, restore the previous phase (always Active).                                                       |

### Queries

| Function                             | Returns                                                                              |
| ------------------------------------ | ------------------------------------------------------------------------------------ |
| `get_config()`                       | `VaultConfig`                                                                        |
| `get_admin()`                        | OZ admin (governor/timelock) — `Option<Address>`                                     |
| `get_lending_pool()`                 | Total funded amount                                                                  |
| `get_investor_deposit(investor)`     | Individual deposit                                                                   |
| `get_investors()`                    | List of all investors                                                                |
| `get_emi()`                          | Investor installment (EMI) amount                                                    |
| `get_required_payment()`             | Standard-period **gross** payment: `ceil(EMI × 100 / split_ratio)`                   |
| `get_current_period_payment()`       | Gross payment for the current period (smaller on the final stretch); 0 if not Active |
| `get_emi_pool()` / `get_cash_pool()` | Pool balances                                                                        |
| `get_payments_made()`                | Payments-made counter (schedule timing only — does **not** gate completion)          |
| `get_outstanding()`                  | Remaining principal                                                                  |
| `get_next_due()`                     | Next payment due date                                                                |
| `get_claimable(investor)`            | Available yield to claim                                                             |
| `get_late_fees()`                    | Accumulated late fees                                                                |
| `get_missed_payments()`              | Consecutive missed payments                                                          |
| `get_grace_period()`                 | Grace period in seconds                                                              |
| `get_reneg_proposal()`               | Current renegotiation proposal                                                       |
| `get_token_cap()`                    | Debt-token supply cap                                                                |
| `is_allowlisted(investor)`           | Whether an investor is allowlisted                                                   |
| `get_allowlist()`                    | Allowlisted investors                                                                |

## SEP-41 Debt Token

The vault itself is the debt-token contract — it implements OZ `FungibleToken` and `FungibleBurnable` directly. Tokens are minted 1:1 with the **net** deposit and represent the investor's share. The cap equals the principal.

Debt tokens are **non-transferable** — `transfer`, `transfer_from`, and `approve` panic on call. This keeps yield claims tied to the original depositor.

| Function                                             | Description                |
| ---------------------------------------------------- | -------------------------- |
| `total_supply()` / `balance(account)`                | Supply and balance queries |
| `transfer(from, to, amount)`                         | ❌ Disabled — panics        |
| `transfer_from(spender, from, to, amount)`           | ❌ Disabled — panics        |
| `approve(owner, spender, amount, live_until_ledger)` | ❌ Disabled — panics        |
| `allowance(owner, spender)`                          | Allowance query (always 0) |
| `decimals()` → 7                                     | Token decimals             |
| `name()` → "Trilobyte Vault Token"                   | Token name                 |
| `symbol()` → "tVLT"                                  | Token symbol               |
| `burn(from, amount)` / `burn_from(...)`              | Burn tokens                |

## Payment Math (`math.rs`)

All math is integer-only with 10¹² internal scaling.

| Function                                       | Description                                                                                                           |
| ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `calculate_emi(principal, annual_bps, term)`   | Standard amortisation, ceiling-rounded (handles 0% as equal installments)                                             |
| `required_gross_payment(emi_inv, split_ratio)` | **Gross-up:** minimum gross so the EMI-pool share covers a full installment: `ceil(emi_inv × 100 / split_ratio)`      |
| `split_payment(amount, split_ratio)`           | Split a gross payment into EMI portion (`amount × split_ratio / 100`) + cash portion (remainder favours the borrower) |
| `split_emi(outstanding, emi, annual_bps)`      | Break an EMI into interest + principal components                                                                     |
| `total_interest(principal, annual_bps, term)`  | Total interest over the lifetime                                                                                      |
| `total_repayment(principal, annual_bps, term)` | Total borrower repayment                                                                                              |
| `calculate_protocol_fee(amount, fee)`          | Protocol fee (`fee` in 7-decimal format)                                                                              |
| `late_fee(overdue, penalty_bps)`               | One month's penalty interest                                                                                          |

### How `receive_payment` settles a payment

The borrower pays a **gross** amount because investors only receive `split_ratio %` of each payment:

1. **Required gross:** `g_min = required_gross_payment(needed_emi_share, split_ratio)` where `needed_emi_share` is a full investor installment, or — on the final stretch — just enough to clear `outstanding + interest`. A **min-payment tolerance band** is applied: `min_payment = g_min − g_min × min_emi_tolerance / 10_000`. Payments below `min_payment` are rejected (`InvalidAmount`).
2. **Split first:** the gross is split into the EMI-pool share (`split_ratio %`) and the cash share (remainder).
3. **Fee from cash only:** the protocol fee is computed on the gross but **charged to the cash share**, never the EMI/investor share, so investor amortisation is independent of the fee. If the fee would exceed the cash share it is **clamped** and a `ProtocolFeeClamped` event is emitted (only possible if the protocol fee is raised above `(100 − split_ratio) %`).
4. **Amortise on EMI share:** outstanding is reduced by the principal component of the EMI-pool share only — the cash pool is the borrower's working capital, not debt repayment.
5. **Completion** is gated on `outstanding == 0` — **never** on the payments-made counter. Early payments (`now < next_due`) are rejected.

## Events (20)

| Event                          | Key Fields                                                                  | When                      |
| ------------------------------ | --------------------------------------------------------------------------- | ------------------------- |
| `VaultCreated`                 | borrower, principal, loan\_term, interest\_rate                             | Constructor               |
| `InvestorDeposited`            | investor, amount, total\_funded                                             | Deposit                   |
| `InvestorWithdrew`             | investor, amount                                                            | Withdrawal                |
| `VaultFullyFunded`             | total\_funded                                                               | Fully funded              |
| `LoanDisbursed`                | borrower, amount                                                            | Disbursement              |
| `PhaseTransition`              | from, to                                                                    | Phase change              |
| `PaymentReceived`              | payer, total\_amount, emi\_share, cash\_share, payment\_number, outstanding | Payment                   |
| `YieldClaimed`                 | investor, amount                                                            | Claim                     |
| `CashWithdrawn`                | borrower, amount                                                            | Cash withdrawal           |
| `LateFeeApplied`               | fee, total\_late\_fees, missed\_payments                                    | Late fee                  |
| `VaultDefaulted`               | borrower, outstanding, missed\_payments                                     | Default                   |
| `RenegotiationProposed`        | manager, new\_interest\_rate, new\_loan\_term                               | Proposal                  |
| `RenegotiationFinalized`       | new\_interest\_rate, new\_loan\_term, new\_emi                              | Terms applied             |
| `RenegotiationRejected`        | investor                                                                    | Proposal cancelled        |
| `ProtocolFeeCollected`         | fee\_amount, treasury, source                                               | Fee taken                 |
| `ProtocolFeeClamped`           | configured\_fee, charged\_fee                                               | Fee clamped to cash share |
| `InvestorAllowlisted`          | investor                                                                    | Added to allowlist        |
| `InvestorRemovedFromAllowlist` | investor                                                                    | Removed from allowlist    |
| `VaultFundingExpired`          | deadline, total\_funded, refunded\_investors                                | Funding expired           |
| `VaultApprovalExpired`         | deadline, total\_funded, refunded\_investors                                | Approval expired          |

## Error Codes

| Code | Name                         | Description                                                                      |
| ---- | ---------------------------- | -------------------------------------------------------------------------------- |
| 1    | `InvalidPhase`               | Wrong lifecycle phase                                                            |
| 2    | `InvalidAmount`              | Amount out of bounds / below min payment                                         |
| 3    | `InvalidTerm`                | Term outside limits                                                              |
| 4    | `InvalidRate`                | Rate below minimum                                                               |
| 5    | `AlreadyFunded`              | Already fully funded                                                             |
| 6    | `ExceedsPrincipal`           | Deposit exceeds principal                                                        |
| 7    | `NoDeposit`                  | No deposit found                                                                 |
| 8    | `InsufficientDeposit`        | Withdrawing too much                                                             |
| 9    | `NotFullyFunded`             | Not yet fully funded                                                             |
| 10   | `InvalidSplitRatio`          | Not 1–99                                                                         |
| 11   | `AssetNotSupported`          | Token not whitelisted                                                            |
| 12   | `BorrowerNotApproved`        | Caller is not the manager                                                        |
| 13   | `PaymentNotDue`              | Payment not yet due (early payment)                                              |
| 14   | `LoanFullyRepaid`            | All payments made                                                                |
| 15   | `Unauthorized`               | Not authorised                                                                   |
| 16   | `NothingToClaim`             | No yield available                                                               |
| 17   | `InsufficientCashPool`       | Cash pool too low                                                                |
| 18   | `NotOverdue`                 | Can't apply late fee                                                             |
| 19   | `AlreadyDefaulted`           | Already defaulted                                                                |
| 20   | `WithinGracePeriod`          | Too early for default                                                            |
| 21   | `NoProposalExists`           | No renegotiation proposal                                                        |
| 22   | `ProposalAlreadyExists`      | Proposal pending                                                                 |
| 25   | `NotAllowlisted`             | Not on allowlist                                                                 |
| 26   | `AlreadyAllowlisted`         | Already on allowlist                                                             |
| 27   | `FundingDeadlineNotReached`  | Too early                                                                        |
| 28   | `ApprovalDeadlineNotReached` | Too early                                                                        |
| 29   | `NoDeadlineSet`              | No deadline configured                                                           |
| 30   | `TooManyInvestors`           | Max investors (100) reached                                                      |
| 31   | `MathOverflow`               | Arithmetic overflow in a math helper                                             |
| 32   | `CollateralPledgeRequired`   | Collateral-backed vault but no `pledge_id` at disbursement                       |
| 33   | `CollateralPledgeNotFound`   | The supplied `pledge_id` has no active pledge                                    |
| 34   | `CollateralPledgeInvalid`    | Pledge mismatch (controller / token / borrower / recovery / insufficient amount) |

{% hint style="info" %}
Codes 23 and 24 are intentionally unused (the enum jumps from 22 to 25).
{% endhint %}


---

# 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 dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.trilobyte.finance/technical-documentation/vault-contract.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
