# Trilobyte Protocol

Decentralised lending infrastructure on Stellar

> Real-world businesses borrow working capital; investors earn yield on incoming cash flows.

{% hint style="info" %}
**Trilobyte is currently in active development on Stellar Testnet.** Smart contracts are undergoing auditing and are not yet deployed on mainnet.
{% endhint %}

## What is Trilobyte?

Trilobyte is a **decentralised lending protocol** built on Stellar's [Soroban](https://soroban.stellar.org/) smart contract platform. It connects businesses that need working capital with investors who want transparent, yield-bearing exposure to real-world cash flows.

Each loan is represented by a **Vault** — a smart contract that manages the full lending lifecycle from fundraising to repayment. Pool Managers underwrite and structure loans, while investors fund them and earn yield from the borrower's periodic repayments.

## How It Works

1. A **Pool Manager** evaluates a business and structures a loan
2. **Investors** fund the loan by depositing into the Vault
3. The **Borrower** receives the capital and makes periodic repayments
4. **Investors** earn yield from the borrower's repayments

## Key Features

* **On-chain transparency** — Every deposit, payment, and claim is recorded on Stellar
* **SEP-41 debt tokens** — Investors receive non-transferable tokens representing their share of the vault
* **Manager accountability** — Pool managers stake collateral that gets slashed on default
* **Flexible terms** — Interest rate, loan term, split ratio, and deadlines are all configurable
* **Timelock governance** — Critical protocol changes go through a time-delayed governance process
* **Built on OpenZeppelin** — Access Control, Pausable, Upgradeable, and FungibleToken modules

## Quick Links

{% content-ref url="/pages/BO0jwYHMKw70MCbWIMev" %}
[What is Trilobyte?](/overview/what-is-trilobyte)
{% endcontent-ref %}

{% content-ref url="/pages/pUXNhyP7BZvrtwxylhfm" %}
[How a Loan Works](/overview/how-a-loan-works)
{% endcontent-ref %}

{% content-ref url="/pages/hESn6qYYznXU1AzbDRYm" %}
[For Investors](/user-guides/for-investors)
{% endcontent-ref %}

{% content-ref url="/pages/oD1fHVZhSbqL2HjWunpa" %}
[For Pool Managers](/user-guides/for-pool-managers)
{% endcontent-ref %}

{% content-ref url="/pages/IMAjfTm4YFFh68HCfZCC" %}
[Smart Contract Architecture](/technical-documentation/architecture)
{% endcontent-ref %}


# What is Trilobyte?

Trilobyte is a **decentralised lending protocol** built on Stellar's Soroban smart contract platform. It connects businesses that need working capital with investors who want transparent, yield-bearing exposure to real-world cash flows.

## The Problem

Small and medium-sized businesses often struggle to access affordable credit. Traditional lending is slow, opaque, and gatekept by institutions with misaligned incentives. Businesses with strong incoming cash flows — invoices, receivables, contracts — may still be denied credit because they lack conventional collateral or credit history.

## The Trilobyte Solution

Trilobyte creates a transparent marketplace where:

* **Businesses** can borrow against their future cash flows without the friction of traditional finance
* **Investors** can earn yield by funding these loans, with full on-chain visibility into repayments
* **Pool Managers** act as professional underwriters who evaluate businesses, structure loan terms, and stake their own capital as a guarantee

Every loan is represented by a **Vault** — a dedicated smart contract that manages the entire lending lifecycle: fundraising, disbursement, repayment tracking, yield distribution, and settlement.

## Why Stellar?

Trilobyte is built on [Soroban](https://soroban.stellar.org/), Stellar's smart contract platform, for several reasons:

* **Low transaction costs** — Stellar's fee model makes micro-payments and frequent repayments practical
* **Fast finality** — Transactions settle in seconds, not minutes
* **SEP-41 token standard** — Native fungible token support for debt tokens
* **Growing DeFi ecosystem** — Soroban provides a modern Rust-based smart contract environment with a growing developer community
* **Global reach** — Stellar is designed for cross-border payments and financial inclusion

## Built With

* [**Soroban SDK v23.5.2**](https://soroban.stellar.org/) — Smart contract framework
* [**OpenZeppelin Stellar Contracts v0.6.0**](https://docs.openzeppelin.com/stellar-contracts) — Battle-tested security modules (Access Control, Pausable, Upgradeable, FungibleToken, Timelock Controller)
* **Rust** — Systems-level safety and performance
* **TypeScript** — Deployment scripts and client SDK


# Key Participants

Trilobyte's lending model involves four key participants, each with distinct roles and responsibilities.

```
┌─────────────┐         ┌──────────────┐         ┌─────────────┐
│  Borrower   │◄────────│  Pool Manager │────────►│  Investor   │
│ (business)  │  loan   │ (underwriter) │  yield  │ (lender)    │
└─────────────┘         └──────────────┘         └─────────────┘
                              │
                     underwrites & monitors
                              │
                        ┌─────────────┐
                        │   Protocol  │
                        │  (Governor) │
                        └─────────────┘
```

## Borrower

A **business** that needs working capital. The borrower receives the loan proceeds once the vault is fully funded and approved, and repays through incoming revenue over the loan term.

**Key responsibilities:**

* Apply for a loan through a Pool Manager
* Make periodic repayments on schedule
* Withdraw available funds from the cash pool

**On-chain actions:** `receive_payment`, `withdraw_cash`

## Pool Manager

A **professional underwriter** who evaluates businesses, negotiates loan terms, and manages the vault throughout its lifecycle. Pool Managers are the bridge between borrowers and investors.

**Key responsibilities:**

* Perform due diligence on borrowers
* Structure loan terms (principal, rate, term, split ratio)
* Create vaults via the Factory contract
* Deposit collateral (skin in the game)
* Approve and disburse funded loans
* Monitor repayments and apply late fees when necessary
* Propose renegotiation when terms need adjustment

**Requirements:**

* Must be approved by the protocol's Operations Admin
* Must deposit collateral covering a percentage of each vault's principal
* Subject to credit limits that cap total outstanding principal
* Flagged as delinquent if a vault defaults — blocked from creating new vaults until cleared

**On-chain actions:** `create_vault`, `deposit_collateral`, `approve_and_disburse`, `apply_late_fee`, `propose_renegotiation`, `approve_renegotiation`, `reject_renegotiation`

## Investor

Anyone who funds a loan and earns yield from the borrower's repayments. Investors deposit funds into a vault during its fundraising phase and receive SEP-41 debt tokens representing their share.

**Key responsibilities:**

* Evaluate available vaults and their terms
* Deposit funds during the RaisingFunds phase
* Claim yield from the EMI pool as repayments come in
* Monitor vault health and repayment status

**In permissioned vaults**, investors must be allowlisted by the Pool Manager before they can deposit.

**On-chain actions:** `deposit`, `withdraw`, `claim_yield`

## Governor

The **protocol administration** that manages global settings and infrastructure. Governance actions are routed through a **Timelock** contract that enforces a delay on critical changes.

The Governor manages three roles:

| Role                 | Who                 | Responsibilities                                                                                              |
| -------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------- |
| **Admin**            | Governor / Timelock | Whitelist assets, set treasury, register the factory, upgrade contracts                                       |
| **Security Admin**   | Security team       | Pause/unpause the protocol in emergencies                                                                     |
| **Operations Admin** | Operations team     | Approve/remove pool managers, set credit limits, clear delinquency, **update global settings & protocol fee** |

**On-chain actions:** `whitelist_asset`, `set_treasury_address`, `set_factory`, `pause`, `unpause`, `update_settings`, `set_protocol_fee`, `approve_pool_manager`, `remove_pool_manager`, `set_manager_credit_limit`, `clear_delinquency`

{% hint style="info" %}
Note: changing global settings and the protocol fee is an **Operations Admin** action and is not currently timelocked. See the [Governance](/protocol-mechanics/governance) page.
{% endhint %}


# How a Loan Works

A Trilobyte loan follows a structured lifecycle from creation to settlement. Here's how a typical loan flows through the protocol.

## Step-by-Step

### 1. Vault Creation

A **Pool Manager** evaluates a business's creditworthiness and negotiates loan terms off-chain. Once agreed, the manager creates a vault via the **Factory** contract with the following parameters:

* **Principal** — The loan amount (e.g. 100,000 USDC)
* **Interest rate** — Annual rate in basis points (e.g. 1000 = 10%)
* **Loan term** — Duration in months (e.g. 12)
* **Split ratio** — Percentage of each repayment routed to the EMI pool for investors (e.g. 80%)
* **Funding deadline** — Optional deadline for full funding
* **Approval deadline** — Optional deadline for manager approval after funding
* **Grace period** — Window before default can be triggered after a missed payment
* **Permissioned** — Whether only allowlisted investors can participate

The Factory validates all parameters against global settings, checks the manager's collateral and credit limit, and deploys a new Vault smart contract.

### 2. Fundraising

The vault enters the **RaisingFunds** phase. Investors deposit funds and receive **SEP-41 debt tokens** at a 1:1 ratio. A 0.5% protocol fee is deducted from each deposit.

The vault automatically transitions to **AwaitingApproval** once the full principal is raised.

{% hint style="info" %}
If a funding deadline is set and the vault fails to raise the full principal by that date, anyone can call `check_funding_expiry()` to cancel the vault and refund all investors.
{% endhint %}

### 3. Approval & Disbursement

The **Pool Manager** reviews the funded vault and calls `approve_and_disburse`. This:

* Disburses the funded amount to the borrower
* Calculates the EMI (Equated Monthly Instalment)
* Sets the first payment due date (30 days from disbursement)
* Transitions the vault to **Active**

{% hint style="info" %}
If an approval deadline is set and the manager fails to approve by that date, anyone can call `check_approval_expiry()` to cancel the vault and refund investors.
{% endhint %}

### 4. Repayments

The **Borrower** makes periodic repayments. Each payment is processed as follows:

1. The payment is **split** by the `split_ratio`:
   * **EMI pool** — Investor yield (e.g. 80%)
   * **Cash pool** — Borrower operating capital (e.g. 20%)
2. The 0.5% protocol fee is taken from the **cash share** (never the EMI/investor share) and sent to the treasury
3. The outstanding principal is amortised
4. The next due date is set; the loan completes once the **outstanding balance reaches zero**

### 5. Yield Distribution

**Investors** can call `claim_yield` at any time during the Active or FullyRepaid phases to claim their pro-rata share of the EMI pool. An investor's share is proportional to their debt token balance relative to the total supply.

$$
\text{claimable} = \frac{\text{balance}}{\text{total\_supply}} \times \text{emi\_pool} - \text{already\_claimed}
$$

### 6. Settlement

When all payments are made, the vault transitions to **FullyRepaid**. The manager's collateral is released. Once all investors have claimed their yield and the borrower has withdrawn remaining cash, the vault can be finalised — transitioning to the terminal **Finalized** state.

## Visual Overview

```
Pool Manager creates vault
         │
         ▼
    ┌─────────────┐
    │ RaisingFunds │ ◄── Investors deposit
    └──────┬──────┘
           │ fully funded
           ▼
   ┌───────────────┐
   │AwaitingApproval│ ◄── Manager reviews
   └───────┬───────┘
           │ approve & disburse
           ▼
      ┌────────┐
      │ Active │ ◄── Borrower makes payments
      └───┬────┘     Investors claim yield
          │
    ┌─────┴──────┐
    │            │
    ▼            ▼
┌──────────┐ ┌───────────┐
│FullyRepaid│ │ Defaulted │
└────┬─────┘ └───────────┘
     │
     ▼
 ┌──────────┐
 │ Finalized │
 └──────────┘
```


# How Underwriting Works

Underwriting is the heart of Trilobyte. Before a loan is ever created, a **Pool Manager** assesses the borrower and decides whether to lend, how much, and on what terms. Unlike crypto lending that only asks *"what assets do you already hold?"*, Trilobyte underwrites on the **real economy** — a business's actual cash flow and, where applicable, real-world assets pledged as collateral.

## Underwritten on verified cash flow

The foundation of a Trilobyte assessment is **real, verifiable revenue** — not self-reported numbers.

A borrower connects a **trusted source of their cash flow** — for example a payment processor, merchant account, or accounting system. Trilobyte reads the borrower's incoming payment history directly from that source, so the loan is underwritten on what the business **actually earns**, not on figures typed into a form.

{% hint style="info" %}
Cash-flow sources are **pluggable**. A borrower can connect different kinds of verified revenue data; the underwriting methodology is the same regardless of the source.
{% endhint %}

This is what lets Trilobyte serve creditworthy businesses that traditional lenders turn away for lacking conventional collateral or a long credit file — their **payment history is the evidence**.

## The methodology — the Five C's of credit

Trilobyte's risk assessment follows the long-established **Five C's of credit**, the same framework professional lenders have used for decades:

* **Capacity** — Can the business afford the loan? Driven by the connected cash-flow data (revenue level, trend, stability, and debt-service coverage).
* **Capital** — The financial health of the business (its own stake versus its existing obligations).
* **Collateral** — What backs the loan if it isn't repaid (the manager's stake, and any real-world asset the borrower pledges).
* **Character** — The borrower's track record and reliability.
* **Conditions** — The market and sector the business operates in.

Each factor contributes to a single, transparent **risk grade**.

## Data-driven, with professional judgment

Trilobyte blends two inputs:

* **Auto-derived from connected data** — factors the cash-flow source can prove directly, such as average revenue, revenue trend, income stability, and how comfortably the business's cash flow covers the proposed repayments.
* **The manager's professional assessment** — factors that come from documents, references, or the manager's own diligence (balance-sheet health, repayment history, pledged collateral).

The data leads where it has signal; the manager fills in what the data can't see. The two are **additive** — a manager's input never overrides what the verified data shows.

## Affordability comes first

A loan must **fit the borrower's cash flow**. Trilobyte's underwriting requires that a business's verified income comfortably covers the repayments before a manager approves a loan — so a borrower is never lent more than they can realistically service, no matter how strong the rest of the profile looks.

## Skin in the game

Underwriting isn't just an opinion. Every Pool Manager **stakes their own capital** as collateral against the loans they originate. If a loan defaults, the manager's stake is at risk first — aligning the underwriter's incentives directly with investors'.

## Transparent terms, private model

Every loan's **terms — amount, rate, and repayment schedule — are recorded on-chain** for investors to verify before they fund it. Those terms are set by this documented assessment rather than a black box, so investors lend against a clear, methodology-driven grade.

What stays proprietary is the **exact model** a manager uses — the specific weightings, thresholds, and scoring logic are part of an underwriter's edge, just as they are at any lender. Trilobyte standardises the *framework* and the *transparency*, while leaving the *judgment* to professionals.


# Collateral & Real-World Assets

Trilobyte loans can be backed by two complementary forms of collateral. Both protect investors — and together they let the protocol serve a wide range of credit, from pure cash-flow lending to asset-backed lending against **tokenized real-world assets**.

## Manager collateral (skin-in-the-game)

Every loan is backed by the **Pool Manager's own staked capital**. When a manager originates a loan, a portion of their staked collateral (a percentage of the principal, set by the protocol's collateral ratio) is **locked** for the life of the loan.

* If the loan is **repaid**, the collateral is released back to the manager.
* If the loan **defaults**, the collateral is **slashed** to compensate investors — it is first-loss capital.

This aligns the underwriter's incentives directly with investors': the manager loses their own money *before* investors do. *(See* [*For Pool Managers*](/user-guides/for-pool-managers)*.)*

## Borrower collateral (real-world assets)

A loan can *additionally* require the **borrower to pledge an asset** as collateral — including a **tokenized real-world asset**. The pledge is held in a dedicated on-chain **escrow** for the life of the loan:

1. **Pledge** — before disbursement, the borrower locks the asset in escrow, naming the vault as its controller.
2. **Verify** — at disbursement, the vault checks the pledge (asset, amount, and recovery party) against the loan's terms before releasing any funds.
3. **Release or seize** — when the loan is fully repaid, the asset is **released** back to the borrower; if the loan defaults, it is **seized** and transferred to the designated recovery party.

This is what lets Trilobyte extend beyond cash-flow lending into **asset-backed credit**: any asset that can be represented as an on-chain token — invoices, receivables, or tokenized real-world assets — can serve as collateral, with the protocol enforcing custody and recovery automatically.

{% hint style="info" %}
A loan can use **manager collateral alone** (cash-flow underwriting), or **manager collateral plus a borrower's pledged asset** (asset-backed). The borrower-collateral parameters — the escrow contract, collateral token, minimum amount, and recovery party — are set per vault at creation. The on-chain custody is handled by a dedicated escrow contract (see the [Collateral Escrow Contract](https://github.com/orunpay/trilobite-docs/blob/development/technical-docs/collateral-escrow-contract.md) for the mechanics).
{% endhint %}


# Vault Types

Trilobyte supports two types of vaults that determine who can invest in a loan.

## Permissionless Vaults

In a **permissionless vault**, any address can deposit funds during the RaisingFunds phase. There are no restrictions on who can participate as an investor.

This is the default model and enables the broadest possible participation in loan funding.

## Permissioned Vaults

In a **permissioned vault**, only addresses that have been **allowlisted** by the Pool Manager can deposit funds. This enables managers to restrict participation to specific investors — for example, those who have completed KYC/AML verification or who meet certain accreditation requirements.

### Managing the Allowlist

The Pool Manager controls the allowlist using two functions:

* `add_to_allowlist(caller, investor)` — Add an investor address
* `remove_from_allowlist(caller, investor)` — Remove an investor address

Only the vault's Pool Manager can modify the allowlist. Investors not on the list will be rejected when attempting to deposit.

### When to Use Permissioned Vaults

Permissioned vaults are appropriate when:

* **Regulatory compliance** requires investor verification (KYC/AML)
* The loan involves **sensitive deal terms** that should only be visible to approved parties
* The Pool Manager wants to **curate the investor base** for a specific deal
* **Institutional investors** require controlled access to the vault

{% hint style="info" %}
The `permissioned` flag is set at vault creation and cannot be changed afterwards. A vault is either permissioned or permissionless for its entire lifecycle.
{% endhint %}


# Vault Lifecycle

Every Trilobyte vault goes through a series of phases that represent the state of the loan. Transitions between phases are triggered by on-chain actions — deposits, approvals, payments, or enforcement calls.

## Phase Diagram

```
RaisingFunds ──► AwaitingApproval ──► Active ──► FullyRepaid   (loan settled · terminal)
                                        │  ↕
                                        │  Renegotiation  (⇄ Active only)
                                        ▼
                                     Defaulted             (terminal)

RaisingFunds / AwaitingApproval ──► Finalized
   (funding or approval deadline expired → investors refunded)
```

## Phases at a Glance

| Phase                | Description                                                                                                              | Who Acts                     |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------ | ---------------------------- |
| **RaisingFunds**     | Vault is open for investor deposits                                                                                      | Investors                    |
| **AwaitingApproval** | Fully funded, waiting for manager to approve and disburse                                                                | Pool Manager                 |
| **Active**           | Loan is live — borrower makes payments, investors claim yield                                                            | Borrower, Investors, Manager |
| **FullyRepaid**      | Loan settled (outstanding reached zero) — investors claim remaining yield. **Terminal.**                                 | Investors, Borrower          |
| **Finalized**        | Terminal state for a vault **cancelled before disbursement** (funding or approval deadline expired) — investors refunded | —                            |
| **Renegotiation**    | Terms are being renegotiated (**from Active only**)                                                                      | Pool Manager                 |
| **Defaulted**        | Borrower failed to repay within the grace period. **Terminal** (manager collateral is slashed).                          | Anyone (trigger)             |

## Transition Rules

* **RaisingFunds → AwaitingApproval**: Automatic when total deposits equal the principal
* **RaisingFunds → Finalized**: Funding deadline expired (permissionless call)
* **AwaitingApproval → Active**: Manager calls `approve_and_disburse`
* **AwaitingApproval → Finalized**: Approval deadline expired (permissionless call)
* **Active → FullyRepaid**: Automatic when the outstanding balance reaches zero (terminal — investors claim any remaining yield here)
* **Active → Renegotiation**: Manager proposes new terms
* **Active → Defaulted**: Grace period expired after a missed payment (permissionless call; terminal)
* **Renegotiation → Active**: Manager approves the proposal (EMI recalculated) — or rejects it, which restores **Active**

*(Defaulted and FullyRepaid are terminal. Renegotiation is only reachable from — and returns to — Active; a defaulted loan cannot be renegotiated because the manager's collateral is already slashed.)*

{% hint style="info" %}
Explore each phase in detail using the sub-pages below.
{% endhint %}


# RaisingFunds

The **RaisingFunds** phase is the initial state of every vault. During this phase, the vault is open for investor deposits.

## What Happens

* Investors call `deposit(investor, amount)` to fund the vault
* For each deposit, 1:1 **SEP-41 debt tokens** (tVLT) are minted to the investor
* A **0.5% protocol fee** is deducted from each deposit and sent to the treasury
* The vault tracks each investor's deposit amount and adds them to the investor list
* In **permissioned vaults**, only allowlisted addresses can deposit

## Key Rules

* Individual deposits can be any amount, but the total cannot exceed the principal
* Investors can **withdraw** their deposit at any time during this phase (debt tokens are burned)
* The phase transitions automatically to **AwaitingApproval** when total deposits equal the principal

## Funding Deadline

If the vault was created with a `funding_deadline`, the vault can be cancelled if it fails to raise the full principal by the deadline.

* Anyone can call `check_funding_expiry()` after the deadline passes
* All investor deposits are refunded
* The manager's locked collateral is released
* The vault transitions directly to **Finalized**

{% hint style="warning" %}
If no funding deadline is set (`funding_deadline = 0`), the vault remains in RaisingFunds indefinitely until fully funded.
{% endhint %}

## Relevant Functions

| Function                             | Caller   | Description                                     |
| ------------------------------------ | -------- | ----------------------------------------------- |
| `deposit(investor, amount)`          | Investor | Deposit funds and mint debt tokens              |
| `withdraw(investor, amount)`         | Investor | Withdraw deposit and burn debt tokens           |
| `check_funding_expiry()`             | Anyone   | Cancel vault if funding deadline passed         |
| `add_to_allowlist(caller, investor)` | Manager  | Add investor to allowlist (permissioned vaults) |


# AwaitingApproval

The **AwaitingApproval** phase begins automatically when the vault reaches full funding. The Pool Manager must now review and approve the loan for disbursement.

## What Happens

* The vault holds the full principal amount deposited by investors
* The Pool Manager reviews the vault and confirms readiness
* The manager calls `approve_and_disburse` to release funds to the borrower

## Approval & Disbursement

When the manager calls `approve_and_disburse(caller)`:

1. The full funded amount is transferred to the **borrower's address**
2. The **EMI** (Equated Monthly Instalment) is calculated based on the principal, interest rate, and loan term
3. The first **payment due date** is set to 30 days from disbursement
4. The **outstanding principal** is initialised to the full principal amount
5. The **missed payments counter** is set to 0
6. The vault transitions to **Active**

{% hint style="info" %}
Only the vault's Pool Manager can call `approve_and_disburse`. This is verified on-chain through the manager address stored in the vault configuration.
{% endhint %}

## Approval Deadline

If the vault was created with an `approval_deadline`, the vault can be cancelled if the manager fails to approve by the deadline.

* Anyone can call `check_approval_expiry()` after the deadline passes
* All investor deposits are refunded
* The manager's locked collateral is released
* The vault transitions directly to **Finalized**

This protects investors from having their capital locked indefinitely in a funded but unapproved vault.

## Relevant Functions

| Function                       | Caller  | Description                              |
| ------------------------------ | ------- | ---------------------------------------- |
| `approve_and_disburse(caller)` | Manager | Approve loan and disburse to borrower    |
| `check_approval_expiry()`      | Anyone  | Cancel vault if approval deadline passed |


# Active

The **Active** phase is the core operating state of the vault. The loan has been disbursed to the borrower, and periodic repayments are expected.

## What Happens

* The **Borrower** makes periodic repayments via `receive_payment`
* Each payment is split between the **EMI pool** (investor yield) and the **Cash pool** (borrower operating capital)
* **Investors** can claim their pro-rata share of accumulated yield at any time
* The **Borrower** can withdraw from the cash pool at any time
* The **Pool Manager** monitors repayment health and enforces late fees if necessary

## Payment Processing

When a payment is received:

1. A **0.5% protocol fee** is deducted and sent to the treasury
2. The remainder is split by the vault's `split_ratio`:
   * EMI pool portion (e.g. 80%) → available for investor claims
   * Cash pool portion (e.g. 20%) → available for borrower withdrawal
3. The **outstanding principal** is amortised (reduced by the principal component of the EMI)
4. The **payment counter** advances
5. The **next due date** is set to 30 days forward
6. The **missed payments counter** resets to 0

## Yield Claims

Investors call `claim_yield(investor)` to claim their share of the EMI pool:

$$
\text{claimable} = \frac{\text{balance}}{\text{total\_supply}} \times \text{emi\_pool} - \text{already\_claimed}
$$

Claims are available during both the **Active** and **FullyRepaid** phases.

## Cash Withdrawals

The borrower can call `withdraw_cash(amount)` to withdraw available funds from the cash pool. This represents the borrower's portion of incoming repayments that they can use for operations.

## Transition Out

From Active, the vault can transition to:

* **FullyRepaid** — Automatically when all payments are made
* **Renegotiation** — When the manager proposes new terms
* **Defaulted** — When the grace period expires after a missed payment

## Relevant Functions

| Function                                    | Caller   | Description                             |
| ------------------------------------------- | -------- | --------------------------------------- |
| `receive_payment(payer, amount)`            | Anyone   | Submit a repayment                      |
| `claim_yield(investor)`                     | Investor | Claim pro-rata EMI pool share           |
| `withdraw_cash(amount)`                     | Borrower | Withdraw from cash pool                 |
| `apply_late_fee(caller)`                    | Manager  | Apply late fee penalty                  |
| `propose_renegotiation(caller, rate, term)` | Manager  | Propose new terms                       |
| `check_default()`                           | Anyone   | Trigger default if grace period expired |


# FullyRepaid

The **FullyRepaid** phase is reached when the borrower has made all required payments. The loan is complete, and all that remains is for investors and the borrower to collect their funds.

## What Happens

* The vault transitions here **automatically** when the final payment is received
* The **Pool Manager's collateral** is released (unlocked via cross-contract call to Globals)
* The manager's **outstanding principal** is decremented in Globals
* **Investors** can continue to claim any unclaimed yield from the EMI pool
* The **Borrower** can withdraw any remaining funds from the cash pool

## What's Available

| Pool          | Who Can Access | Function                |
| ------------- | -------------- | ----------------------- |
| **EMI pool**  | Investors      | `claim_yield(investor)` |
| **Cash pool** | Borrower       | `withdraw_cash(amount)` |

## Transition

Once all funds have been distributed, the vault transitions to **Finalized** — the terminal state.

{% hint style="success" %}
A vault reaching FullyRepaid is the ideal outcome. The borrower has fulfilled their obligations, investors have earned their yield, and the manager's collateral is returned.
{% endhint %}


# Finalized

The **Finalized** phase is the terminal state for a vault that was **cancelled before its loan ever went live** — it either failed to fully fund, or wasn't approved in time. Investors are refunded and no further actions can be taken.

{% hint style="info" %}
A loan that *was* disbursed and then repaid ends in **FullyRepaid**, which is its own terminal state — it does **not** move to Finalized. Finalized is reached only via the two deadline-expiry paths below.
{% endhint %}

## How a Vault Gets Finalized

| Path                          | Trigger                                           |
| ----------------------------- | ------------------------------------------------- |
| **Funding deadline expired**  | RaisingFunds → Finalized (investors refunded)     |
| **Approval deadline expired** | AwaitingApproval → Finalized (investors refunded) |

## What Happens

* Investor deposits are refunded; any borrower collateral pledged is returned
* No further deposits, payments, claims, or withdrawals are possible
* The vault contract remains on-chain as a permanent record

{% hint style="info" %}
Finalized vaults serve as an immutable audit trail. All events — deposits, payments, claims, phase transitions — remain queryable on the Stellar ledger.
{% endhint %}


# Renegotiation

The **Renegotiation** phase allows the Pool Manager to propose modified loan terms when circumstances change. This happens from the **Active** phase — a struggling loan can be restructured *before* it defaults. (Once a loan has defaulted, the manager's collateral is already slashed and it cannot be renegotiated.)

## When to Renegotiate

Renegotiation is useful when:

* The borrower is struggling with payments and needs adjusted terms
* Market conditions have changed and rates need updating
* A struggling loan needs restructuring *before* it defaults
* The loan term needs to be extended or shortened

## How It Works

### 1. Propose

The Pool Manager calls `propose_renegotiation(caller, new_interest_rate, new_loan_term)`:

* The vault's current phase (**Active**) is saved
* A `RenegotiationProposal` is stored with the new rate, new term, and timestamp
* The vault transitions to the **Renegotiation** phase

### 2. Approve or Reject

The Pool Manager then either approves or rejects the proposal:

**If approved** (`approve_renegotiation`):

* The vault's interest rate and loan term are updated
* The **EMI is recalculated** based on the **outstanding principal** (not the original principal)
* Payment tracking is reset: `payments_made = 0`, `missed_payments = 0`, `late_fees = 0`
* The **next due date** is set to 30 days from now
* The vault transitions back to **Active**

**If rejected** (`reject_renegotiation`):

* The proposal is discarded
* The vault returns to the **Active** phase

{% hint style="warning" %}
Only one renegotiation proposal can exist at a time. A new proposal cannot be created while one is pending.
{% endhint %}

## Key Details

* Only the vault's **Pool Manager** can propose, approve, or reject renegotiations
* The EMI recalculation uses the **remaining outstanding principal**, not the original principal — this means the new payment schedule reflects what's actually owed
* Renegotiation is a **pre-default** tool — it lets a manager restructure a struggling loan while it's still Active. Once a loan defaults, the manager's collateral is slashed and the loan can no longer be renegotiated.

## Relevant Functions

| Function                                    | Caller  | Description                             |
| ------------------------------------------- | ------- | --------------------------------------- |
| `propose_renegotiation(caller, rate, term)` | Manager | Propose new terms                       |
| `approve_renegotiation(caller)`             | Manager | Apply new terms, recalculate EMI        |
| `reject_renegotiation(caller)`              | Manager | Cancel proposal, restore previous phase |
| `get_reneg_proposal()`                      | Anyone  | View current proposal                   |


# Defaulted

The **Defaulted** phase is triggered when a borrower fails to make payments within the allowed grace period. Default has serious consequences for the Pool Manager.

## How Default Happens

1. The borrower misses a payment — the **next due date** passes
2. The Pool Manager may apply **late fees** (18% p.a. penalty)
3. The **grace period** countdown begins (default: 30 days)
4. If no payment is made before `next_due + grace_period`, anyone can call `check_default()`
5. The vault transitions to **Defaulted**

## What Happens on Default

When `check_default()` is called and the grace period has expired:

1. The vault phase transitions to **Defaulted**
2. The Pool Manager's **collateral is slashed** — transferred to a recovery address for investor protection
3. The manager is **marked delinquent** in the Globals contract (`mark_delinquent`)
4. The manager's **outstanding principal** is decremented in Globals
5. A `VaultDefaulted` event is emitted with the outstanding amount and missed payment count

## Consequences for the Manager

* **Collateral slashed** — The locked collateral for this vault is seized
* **Delinquency flag** — The manager is flagged as delinquent in Globals
* **Blocked from new vaults** — The Factory rejects vault creation for delinquent managers
* **Requires manual clearing** — Only an Operations Admin can call `clear_delinquency` after review

{% hint style="danger" %}
Default is the most severe outcome. Pool Managers have strong financial incentives to ensure borrower repayment — their own collateral is at stake.
{% endhint %}

## Default is terminal

Once a loan defaults, the phase is **terminal** — it cannot be renegotiated or returned to Active:

* The Pool Manager's staked **collateral is slashed** to compensate investors.
* The manager is marked **delinquent** in Globals, which blocks them from originating new loans until cleared.
* Restructuring is only possible **before** default — see [Renegotiation](/protocol-mechanics/vault-lifecycle/renegotiation), which is reachable from the Active phase only.

## Relevant Functions

| Function          | Caller | Description                                                                                                  |
| ----------------- | ------ | ------------------------------------------------------------------------------------------------------------ |
| `check_default()` | Anyone | Trigger default once the grace period has expired (slashes manager collateral, marks the manager delinquent) |


# Payments & EMI

Trilobyte uses an **Equated Monthly Instalment (EMI)** model for loan repayments. Each payment is a fixed amount calculated at disbursement, covering both principal and interest.

## EMI Calculation

The EMI is calculated using the standard amortisation formula:

$$
EMI = P \times \frac{r(1+r)^n}{(1+r)^n - 1}
$$

Where:

* $P$ = Principal (loan amount)
* $r$ = Monthly interest rate (annual rate ÷ 12)
* $n$ = Number of monthly payments (loan term)

{% hint style="info" %}
All math is **integer-only** — Soroban has no floating-point support. Trilobyte uses 10¹² internal precision scaling and ceiling-rounds the final EMI to ensure the borrower always repays at least the full amount owed.
{% endhint %}

## Payment Split

Each payment is split into two pools based on the vault's **split ratio**:

| Pool          | Purpose                                                 | Accessible By                |
| ------------- | ------------------------------------------------------- | ---------------------------- |
| **EMI Pool**  | Investor yield — proportional to split ratio (e.g. 80%) | Investors via `claim_yield`  |
| **Cash Pool** | Borrower operating capital — remainder (e.g. 20%)       | Borrower via `withdraw_cash` |

For example, with a split ratio of 80, a **gross** payment of 10,000 USDC splits:

* **8,000 USDC** → EMI pool (investor yield)
* **2,000 USDC** → Cash pool — from which the 0.5% protocol fee is taken (→ \~1,950 USDC to the borrower)

Because only the EMI share repays the loan, the borrower **grosses up** the payment so that `split_ratio%` of it covers one investor instalment — i.e. `gross = ceil(EMI × 100 / split_ratio)`. The contract exposes `get_required_payment` / `get_current_period_payment` for the exact amount due.

## Payment Schedule

* Payments are due every **30 days** (30/360 day-count convention)
* The first payment is due 30 days after disbursement
* Each payment advances the `next_due` date by 30 days
* The loan term sets the EMI schedule, but the loan **completes when the outstanding balance reaches zero** — not on a fixed payment count

## Principal Amortisation

Each EMI payment contains both an interest component and a principal component:

$$
\text{interest} = \text{outstanding} \times \frac{\text{annual\_rate}}{12}
$$

$$
\text{principal} = \text{EMI} - \text{interest}
$$

The outstanding principal decreases with each payment. Early payments are interest-heavy, while later payments are principal-heavy — standard amortisation behaviour.

## Protocol Fee

A **0.5% protocol fee** applies to every repayment — but **after the split, and only from the borrower's cash share**. Investor (EMI) yield is never reduced by the fee:

1. Gross payment received (e.g. 10,000 USDC)
2. Split by ratio first (e.g. 8,000 → EMI pool, 2,000 → cash share)
3. The 0.5% fee (50 USDC) is taken from the cash share → 1,950 to the cash pool, 50 to the treasury

See [Fees](/protocol-mechanics/fees) for the full model (including the fee clamp).

## Yield Claiming

Investors claim their share of the EMI pool proportionally:

$$
\text{claimable} = \frac{\text{balance}}{\text{total\_supply}} \times \text{emi\_pool} - \text{already\_claimed}
$$

* `balance` = Investor's debt token balance
* `total_supply` = Total debt token supply
* `emi_pool` = Total accumulated EMI pool
* `already_claimed` = Amount the investor has already claimed

Claims can be made at any time during the **Active** or **FullyRepaid** phases.

## Token Precision

All amounts use **7 decimal places** (Stellar standard). For example:

* 1 USDC = `10_000_000` (10⁷)
* 0.5% fee = `50_000` in 7-decimal format


# Fees

Trilobyte charges two types of fees: a protocol fee that sustains the protocol, and a late fee that signals vault health.

## Protocol Fee

A **0.5% protocol fee** (a governance parameter, default `50_000` in 7-decimal fixed-point) is collected at two points, and always sent to the **protocol treasury** (the address stored in the Globals contract).

### On investor deposits (funding)

When an investor funds a loan, 0.5% is taken off the deposit and sent to the treasury; the **remainder is what gets lent**. Debt tokens are minted 1:1 with the **net** amount.

> *Example: a 100,000 USDC deposit → 500 USDC to the treasury → 99,500 USDC enters the loan (and 99,500 debt tokens are minted to the investor).*

### On loan repayments — charged to the borrower, never the investors

Each repayment is **first split** by the vault's split ratio (the investor share goes to the **EMI pool**, the remainder to the borrower's **cash pool**). The 0.5% fee — calculated on the gross payment — is then charged **only to the borrower's cash share. The EMI pool is never touched.**

This is a deliberate design choice: **investor amortisation is independent of the fee.** Investors always receive their full split; the protocol fee comes out of the borrower's retained cash.

> *Example (50/50 split, 10,000 gross payment): 5,000 → EMI pool (investors, untouched); 5,000 cash share → 50 fee to the treasury → 4,950 to the borrower's cash pool.*

{% hint style="info" %}
**Fee clamp.** The repayment fee can never exceed the borrower's cash share. It would only bind if the protocol fee were raised above the cash percentage (e.g. above 50% on a 50/50 split). If it ever clamps, a `ProtocolFeeClamped` event is emitted so the treasury shortfall is observable.
{% endhint %}

### Fee configuration

* Default: `50_000` (0.5%).
* Updated via `set_protocol_fee(fee, caller)` on the Globals contract by the **Operations Admin**. *(This action is not currently timelocked — see* [*Governance*](/protocol-mechanics/governance)*.)*

## Late Fees

When a borrower misses a payment deadline, the Pool Manager can apply a **late fee** penalty.

* **Rate**: 18% per annum on the overdue amount — currently **hardcoded in the vault contract** (`1800` bps), not read from a configurable setting.
* **Applied by**: the Pool Manager, via `apply_late_fee(caller)`.
* **Condition**: only when the payment is overdue (`now > next_due`).
* **Effect**: accrues to the vault's `late_fees` accounting balance and advances the schedule to prevent double-charging.

$$
\text{late\_fee} = \text{overdue\_amount} \times \frac{1800\ \text{bps}}{10{,}000 \times 12}
$$

{% hint style="info" %}
Late fees are an **accounting figure** that signals vault health — they accrue in the vault's `late_fees` balance and are emitted via the `LateFeeApplied` event. Note that the default-trigger clock uses an immutable schedule measured from disbursement, so applying a late fee cannot delay when a loan can be marked in default.
{% endhint %}

## Fee Summary

| Fee          | Rate     | Charged on        | Taken from                                       | Recipient |
| ------------ | -------- | ----------------- | ------------------------------------------------ | --------- |
| Protocol fee | 0.5%     | Investor deposits | The deposit                                      | Treasury  |
| Protocol fee | 0.5%     | Loan repayments   | The borrower's cash share (never investor yield) | Treasury  |
| Late fee     | 18% p.a. | Overdue payments  | Accrued (accounting only)                        | Vault     |


# Default & Risk Management

Trilobyte has multiple layers of protection to manage risk and protect investors when borrowers fail to repay.

## Risk Layers

```
Layer 1: Late Fees          → Financial penalty for overdue payments
Layer 2: Grace Period        → Time buffer before default
Layer 3: Manager Collateral  → Skin in the game — slashed on default
Layer 4: Delinquency Tracking → Blocks risky managers from new vaults
Layer 5: Renegotiation       → Restructure terms to avoid default
Layer 6: Credit Limits       → Cap manager exposure
```

## Late Fees

When a payment is overdue (`now > next_due`), the Pool Manager can apply a **late fee**:

* **Rate**: 18% per annum, calculated monthly
* **Effect**: Increments missed payment counter, advances next due date
* **Purpose**: Financial penalty that signals vault distress

## Grace Period

Every vault has a configurable **grace period** (default: 30 days). This is the window between a missed payment due date and when default can be triggered.

* During the grace period, the borrower can still make payments to prevent default
* The manager can apply late fees and attempt to resolve the situation
* After the grace period expires, **anyone** can call `check_default()` to trigger default

## Default Trigger

Default is triggered by calling `check_default()` when:

1. The vault is in the **Active** phase
2. The current time exceeds `next_due + grace_period`

Default is **permissionless** — anyone can trigger it. This prevents managers from colluding with borrowers to avoid consequences.

## Consequences of Default

When default is triggered:

| Action                      | Detail                                                      |
| --------------------------- | ----------------------------------------------------------- |
| **Phase transition**        | Vault moves to `Defaulted`                                  |
| **Collateral slashed**      | Manager's locked collateral is seized for investor recovery |
| **Delinquency flag**        | Manager is marked delinquent in Globals                     |
| **Outstanding decremented** | Manager's outstanding principal is reduced in Globals       |
| **Events emitted**          | `VaultDefaulted`, `ManagerMarkedDelinquent`                 |

## Manager Collateral

Pool Managers must deposit collateral before creating vaults. This is their "skin in the game":

* **Deposit**: `deposit_collateral(manager, token, amount)` — stake tokens in Globals
* **Lock**: When a vault is created via the Factory, collateral is locked proportional to the principal
* **Release**: On `FullyRepaid`, the locked collateral is released
* **Slash**: On `Defaulted`, the locked collateral is slashed and transferred for investor recovery

The **collateral ratio** is a global setting that determines what percentage of the principal must be covered by collateral.

## Delinquency Tracking

Managers with defaulted vaults are flagged as **delinquent**:

* The Factory checks `is_delinquent(manager)` before allowing vault creation
* Delinquent managers are **blocked** from creating new vaults
* Only an **Operations Admin** can clear delinquency via `clear_delinquency(caller, manager)` after review
* A `delinquency_count` tracks the total number of defaults per manager

## Credit Limits

Each Pool Manager has a **maximum credit limit** (`max_credit`) set by the Operations Admin:

* The Factory checks `outstanding_principal + new_principal ≤ max_credit` before deployment
* Prevents a single manager from over-leveraging the protocol
* Can be adjusted via `set_manager_credit_limit(caller, manager, max_credit)`

## Renegotiation as a pre-default workout

Renegotiation is a **pre-default** tool — a way to restructure a struggling loan *before* it tips into default:

* While a loan is **Active**, the manager can `propose_renegotiation` with a new rate and term.
* If approved, the EMI is recalculated on the **outstanding** principal and the vault stays Active.
* Once a loan has **defaulted**, it is terminal — the manager's collateral has already been slashed, so a defaulted loan cannot be renegotiated.

{% hint style="info" %}
Trilobyte's risk model is inspired by traditional structured finance, where first-loss capital (manager collateral), credit limits, and pre-default workouts (renegotiation) work together to protect senior investors.
{% endhint %}


# Governance

Trilobyte uses a **role-based access control** system combined with a **Timelock** for governance delay on critical actions.

## Governance Architecture

```
┌───────────────────────────────────────────────┐
│                  Timelock                      │
│        (OZ TimelockController)                │
│   Proposer / Executor / Canceller roles       │
│   Admin of Globals (governor actions delayed) │
└──────────────────────┬────────────────────────┘
                       │ admin
┌──────────────────────▼────────────────────────┐
│                   Globals                      │
│          (OZ Access + Pausable + Upgradeable)  │
│   Settings · Roles · Fees · Assets · Managers  │
└───────────────────────────────────────────────┘
```

The **Timelock** is set as the admin of the **Globals** contract. This means all admin-level actions must go through a time-delayed governance process.

## Roles

| Role                 | Key         | Assigned To         | Responsibilities                                                           |
| -------------------- | ----------- | ------------------- | -------------------------------------------------------------------------- |
| **Admin**            | `"admin"`   | Governor / Timelock | Global settings, asset whitelist, contract upgrades, treasury, fee updates |
| **Security Admin**   | `"sec_adm"` | Security team       | Emergency pause/unpause of the protocol                                    |
| **Operations Admin** | `"ops_adm"` | Operations team     | Approve/remove pool managers, set credit limits, clear delinquency         |

## Timelock Controller

The Timelock enforces a **minimum delay** on governance operations. This gives protocol participants time to review and react to proposed changes before they take effect.

### Operation Lifecycle

```
Unset → Waiting → Ready → Done
```

1. **Proposer** schedules an operation with a delay
2. The operation enters **Waiting** state
3. After the delay passes, the operation becomes **Ready**
4. **Executor** executes the operation → **Done**

A **Canceller** can cancel any pending operation before execution.

### Admin (Timelock-controlled) actions

The Globals **admin role** is held by the Timelock, so these admin-only actions carry the governance delay:

| Action            | Function               | Impact                                    |
| ----------------- | ---------------------- | ----------------------------------------- |
| Whitelist assets  | `whitelist_asset`      | Enables a token for lending/collateral    |
| Remove assets     | `remove_asset`         | Disables a token                          |
| Update treasury   | `set_treasury_address` | Changes the fee recipient                 |
| Register factory  | `set_factory`          | Authorises the factory to register vaults |
| Upgrade contracts | `upgrade`              | Deploys new contract code                 |

### Non-Timelocked (instant) actions

These are Operations- or Security-Admin actions, executed instantly:

| Action                     | Role           | Function                                       |
| -------------------------- | -------------- | ---------------------------------------------- |
| Pause / unpause            | Security Admin | `pause` / `unpause`                            |
| Approve / remove managers  | Ops Admin      | `approve_pool_manager` / `remove_pool_manager` |
| Set credit limits          | Ops Admin      | `set_manager_credit_limit`                     |
| Clear delinquency          | Ops Admin      | `clear_delinquency`                            |
| **Update global settings** | Ops Admin      | `update_settings`                              |
| **Update protocol fee**    | Ops Admin      | `set_protocol_fee`                             |

{% hint style="warning" %}
**Updating global settings and the protocol fee is an Operations-Admin action and is NOT timelocked** — the Ops Admin can change loan-parameter bounds and the fee instantly. Only the admin-role actions above carry the Timelock delay.
{% endhint %}

## Emergency Powers

The **Security Admin** can pause the entire protocol instantly:

* `pause(caller)` — Halts all state-changing functions protected by `#[when_not_paused]`
* `unpause(caller)` — Resumes normal operation

This is a circuit breaker for emergencies — exploits, critical bugs, or market events that require immediate protocol freeze.

{% hint style="warning" %}
Pausing the protocol prevents new deposits, vault creation, and collateral operations. Existing vaults that are already Active continue to accept payments (the pause is on Globals-level operations).
{% endhint %}

## Self-Administered Timelock

The Timelock is deployed as **self-administered** — it is its own admin. This means:

* No single address can bypass the time delay
* The Timelock's own settings (delay, roles) can only be changed through the Timelock itself
* This creates a fully decentralised governance loop


# Accounts & Sign-In

Trilobyte is designed to feel like a modern financial app, not a crypto wallet. There are **no seed phrases, no browser extensions, and no passwords** — you sign in with a **passkey**, the same way you unlock your phone.

## Passkey accounts

Every Trilobyte account is a **smart account** secured by a **passkey** on your device (Face ID, Touch ID, Windows Hello, or a hardware security key). The passkey is what authorises your actions — funding a loan, approving a disbursement, making a repayment — by signing each transaction.

This gives you the best of both worlds:

* **Self-custodial** — you control your own account; Trilobyte never holds your funds.
* **Nothing to lose or leak** — there's no 12-word phrase to write down, lose, or have phished.

## Creating an account

1. **Choose your role** — Borrower, Investor, or Pool Manager.
2. **Create your passkey** — your device prompts you (Face ID / Touch ID / security key).
3. **You're in** — your smart account is created and you land on your dashboard.

That's it. No email verification loops, no exchange, no crypto to buy first.

## Signing in

On any device where you've registered your passkey, just **sign in with the passkey** — your account and role are recognised automatically and you go straight to your dashboard. No password to remember or reset.

## Stablecoins, not crypto volatility

Everything in Trilobyte is denominated in **stablecoins** (USDC). Loan amounts, deposits, repayments, and yield are all in stable, dollar-denominated value — so you're never exposed to token price swings just to participate.

## No gas, ever

On most blockchains, users must hold the network's "gas" token to pay transaction fees. Trilobyte removes that friction entirely: **network fees are sponsored**, so you never need to hold, buy, or think about a gas token. You only ever deal in stablecoins.

{% hint style="info" %}
**Under the hood.** Trilobyte accounts use **SEP-45** — Stellar's web authentication standard for smart (contract) accounts — so your passkey proves ownership of your on-chain account without a password or seed phrase. Multi-device and backup recovery are on the roadmap.
{% endhint %}


# For Borrowers

This guide explains how businesses interact with Trilobyte to obtain and repay loans.

## Getting a Loan

### 1. Find a Pool Manager

Borrowers work with an approved **Pool Manager** who will:

* Evaluate your business and cash flows
* Perform due diligence and risk assessment
* Structure the loan terms (principal, rate, term, split ratio)
* Create the vault on your behalf

{% hint style="info" %}
Loan terms are negotiated **off-chain** between you and the Pool Manager. The agreed terms are then encoded into a Vault smart contract.
{% endhint %}

### 2. Wait for Funding

Once the vault is created, it enters the **RaisingFunds** phase. Investors deposit funds until the full principal is raised. You don't need to do anything during this phase.

### 3. Receive Disbursement

When the vault is fully funded, the Pool Manager approves and disburses the loan. The full principal amount is transferred directly to your wallet address.

## Making Repayments

Once your loan is active, you make periodic repayments:

* Payments are due every **30 days**
* The payment amount is the calculated **EMI** (Equated Monthly Instalment) — a fixed amount covering principal and interest
* Anyone can submit a payment on your behalf via `receive_payment(payer, amount)`

### What happens to each payment

1. Your payment is **split** by the vault's **split ratio**:
   * **EMI pool** (e.g. 80%) → available for investors to claim as yield
   * **Cash pool** (e.g. 20%) → available for you to withdraw
2. The **0.5% protocol fee** is then taken from **your cash share** (never the investor/EMI share) and sent to the treasury.

{% hint style="info" %}
Because only the EMI share counts toward repaying the loan, the amount due each period is **grossed up** so that `split_ratio%` of it equals one full investor instalment. The app shows you the exact amount to pay.
{% endhint %}

### Staying on schedule

* Make payments **before the due date** to avoid late fees
* If you miss a payment, the Pool Manager can apply an **18% p.a. late fee**
* After the **grace period** (default 30 days), anyone can trigger **default**

{% hint style="danger" %}
Default has serious consequences — the Pool Manager's collateral is slashed and the vault enters a distressed state. Always communicate with your Pool Manager if you're having difficulty making payments.
{% endhint %}

## Withdrawing from the Cash Pool

During the **Active** and **FullyRepaid** phases, you can withdraw your portion of repayments from the cash pool:

* Call `withdraw_cash(amount)` to withdraw available funds
* The cash pool balance is your share of incoming repayments (based on the split ratio)
* You can withdraw any amount up to the current cash pool balance

## Renegotiation

If your circumstances change and you need adjusted terms, work with your Pool Manager:

* The manager can propose **new interest rate and/or loan term**
* If approved, the EMI is recalculated on the **remaining outstanding principal**
* Payment tracking resets with the new schedule
* This can be done from both the Active and Defaulted phases

## Loan Completion

When all EMI payments are made:

1. The vault transitions to **FullyRepaid**
2. You can withdraw any remaining funds from the cash pool
3. The vault eventually moves to **Finalized** (terminal state)

## Key Functions

| Function                         | Description                          |
| -------------------------------- | ------------------------------------ |
| `receive_payment(payer, amount)` | Make a loan repayment                |
| `withdraw_cash(amount)`          | Withdraw from the cash pool          |
| `get_config()`                   | View vault terms and current phase   |
| `get_outstanding()`              | Check remaining principal            |
| `get_next_due()`                 | See next payment due date            |
| `get_emi()`                      | View the EMI amount                  |
| `get_cash_pool()`                | Check available cash pool balance    |
| `get_payments_made()`            | See how many payments have been made |


# For Pool Managers

This guide explains how Pool Managers operate within Trilobyte — from onboarding to vault management.

## What is a Pool Manager?

Pool Managers are **professional underwriters** who bridge borrowers and investors. They evaluate businesses, structure loans, and take financial responsibility for the vaults they create by staking collateral.

## Getting Started

### 1. Get Approved

Pool Managers must be approved by the protocol's **Operations Admin**:

* An Ops Admin calls `approve_pool_manager(manager, caller, max_credit)` — a single call that registers and activates you with a **credit limit**
* Your credit limit caps the total outstanding principal across all your active vaults

### 2. Deposit Collateral

Before creating vaults, you must deposit collateral:

* Call `deposit_collateral(manager, token, amount)` on the Globals contract
* Collateral must cover a percentage of each vault's principal (set by the global `collateral_ratio`)
* Collateral is locked when you create a vault and released when the vault is fully repaid

{% hint style="warning" %}
Your collateral is **slashed** if any of your vaults default. This is your skin in the game — it aligns your incentives with investors.
{% endhint %}

## Creating a Vault

### 1. Structure the Loan

Negotiate terms with the borrower off-chain:

* **Principal** — Loan amount (must be within global min/max bounds)
* **Interest rate** — Annual rate in basis points (must exceed global minimum)
* **Loan term** — Duration in months (must be within global min/max bounds)
* **Split ratio** — Percentage of payments routed to the EMI pool (1–99)
* **Permissioned** — Whether to restrict investors to an allowlist
* **Funding deadline** — Optional deadline for full funding (0 = no deadline)
* **Approval deadline** — Optional deadline for your approval after funding (0 = no deadline)
* **Grace period** — Seconds after a missed payment before default can be triggered

### 2. Deploy via Factory

Call `create_vault(params, salt)` on the Factory contract. The Factory will:

1. Verify you are an approved, active manager
2. Check you are **not delinquent** (no previous defaults)
3. Check your **credit limit** (`outstanding + principal ≤ max_credit`)
4. Validate all loan parameters against global settings
5. **Lock your collateral** proportional to the principal
6. **Increment your outstanding principal**
7. Deploy a new Vault smart contract

### 3. Manage the Allowlist (Permissioned Vaults)

For permissioned vaults, manage who can invest:

* `add_to_allowlist(caller, investor)` — Allow an address to deposit
* `remove_from_allowlist(caller, investor)` — Revoke access

## Managing Active Vaults

### Approve & Disburse

Once the vault is fully funded (phase = AwaitingApproval):

* Call `approve_and_disburse(caller)` to release funds to the borrower
* This calculates the EMI, sets the payment schedule, and transitions to Active

### Monitor Repayments

Track vault health using query functions:

| Function                | What It Shows               |
| ----------------------- | --------------------------- |
| `get_payments_made()`   | Number of payments received |
| `get_outstanding()`     | Remaining principal         |
| `get_next_due()`        | Next payment due date       |
| `get_missed_payments()` | Consecutive missed payments |
| `get_late_fees()`       | Accumulated late fees       |
| `get_emi_pool()`        | Total investor yield pool   |
| `get_cash_pool()`       | Total borrower cash pool    |

### Apply Late Fees

If a payment is overdue (`now > next_due`):

* Call `apply_late_fee(caller)` to apply an 18% p.a. penalty
* This increments the missed payments counter and advances the next due date
* Use this to signal vault distress and incentivise borrower repayment

### Renegotiation

When terms need adjustment:

1. Call `propose_renegotiation(caller, new_interest_rate, new_loan_term)` — enters Renegotiation phase
2. Review the proposal, then either:
   * `approve_renegotiation(caller)` — Apply new terms, recalculate EMI, return to Active
   * `reject_renegotiation(caller)` — Cancel and restore the Active phase

Renegotiation can only be proposed from the **Active** phase — it's a pre-default workout. Once a loan has defaulted, your collateral is already slashed and it can no longer be renegotiated.

## Collateral Management

| Function                                     | Description                                    |
| -------------------------------------------- | ---------------------------------------------- |
| `deposit_collateral(manager, token, amount)` | Stake collateral tokens                        |
| `withdraw_collateral(manager, amount)`       | Withdraw unlocked (staked − locked) collateral |
| `get_manager_collateral(manager)`            | View staked and locked amounts                 |
| `get_available_collateral(manager)`          | View withdrawable balance                      |

### Collateral Lifecycle

```
Deposit → Staked
               ↓ (vault created)
            Locked
               ↓ (vault fully repaid)
            Released → Staked (withdrawable)
               ↓ (vault defaulted)
            Slashed (lost)
```

## Delinquency

If one of your vaults defaults:

* You are marked **delinquent** in the Globals contract
* You are **blocked** from creating new vaults via the Factory
* Your `delinquency_count` is incremented
* Only an **Operations Admin** can clear your delinquency after review

{% hint style="danger" %}
Delinquency is a serious flag. It indicates a failure in underwriting or borrower management. The Operations Admin will review the circumstances before clearing it.
{% endhint %}

## Key Constraints

| Constraint                      | Enforced By |
| ------------------------------- | ----------- |
| Must be approved and active     | Factory     |
| Must not be delinquent          | Factory     |
| Outstanding ≤ credit limit      | Factory     |
| Sufficient collateral           | Factory     |
| Parameters within global bounds | Factory     |


# For Investors

This guide explains how investors participate in Trilobyte to earn yield from real-world lending.

## How Investing Works

Investors earn yield by funding loans through Trilobyte Vaults. When you deposit funds, you receive **SEP-41 debt tokens** representing your share of the vault. As the borrower makes repayments, you can claim your proportional share of the yield.

## Depositing

### 1. Find a Vault

Browse available vaults in the **RaisingFunds** phase. Each vault displays its terms:

* **Principal** — Total loan amount being raised
* **Interest rate** — Annual rate the borrower pays
* **Loan term** — Duration in months
* **Split ratio** — Percentage of repayments allocated to investor yield
* **Pool Manager** — Who underwrote the deal
* **Funding deadline** — When funding must be completed (if set)
* **Permissioned** — Whether the vault requires allowlisting

### 2. Deposit Funds

Call `deposit(investor, amount)` to fund the vault:

* A **0.5% protocol fee** is deducted from your deposit
* You receive debt tokens (tVLT) 1:1 with the **net** amount — so a 10,000 USDC deposit mints 9,950 tVLT
* Your deposit is tracked individually in the vault
* You are added to the vault's investor list

{% hint style="info" %}
On repayments the 0.5% protocol fee is taken from the **borrower's** cash share, never from the EMI pool — so it does **not** reduce your yield.
{% endhint %}

{% hint style="info" %}
For **permissioned vaults**, you must be on the Pool Manager's allowlist before you can deposit. Contact the manager to request access.
{% endhint %}

### 3. Withdraw Before Funding Completes

If the vault hasn't been fully funded yet, you can withdraw your deposit:

* Call `withdraw(investor, amount)` to get your funds back
* Your debt tokens are burned proportionally
* You can withdraw partially or in full

{% hint style="warning" %}
Once the vault is fully funded and transitions to AwaitingApproval, you can no longer withdraw. Your funds are committed.
{% endhint %}

## Debt Tokens (tVLT)

When you deposit, you receive **Trilobyte Vault Tokens (tVLT)** — SEP-41 compliant fungible tokens:

| Property         | Value                                                                                                  |
| ---------------- | ------------------------------------------------------------------------------------------------------ |
| **Name**         | Trilobyte Vault Token                                                                                  |
| **Symbol**       | tVLT                                                                                                   |
| **Decimals**     | 7                                                                                                      |
| **Standard**     | SEP-41 (Stellar fungible token)                                                                        |
| **Cap**          | Equal to the vault's principal                                                                         |
| **Transferable** | **No** — tVLT is non-transferable (it represents your own position; `transfer`/`approve` are disabled) |

Debt tokens represent your share of the vault and determine your proportion of yield.

{% hint style="warning" %}
**Debt tokens are non-transferable.** You cannot transfer, sell, or delegate your debt tokens to another address. The `transfer`, `transfer_from`, and `approve` functions are disabled. This ensures that yield claims remain tied to the original depositor.
{% endhint %}

Available token operations:

* `balance` — Check your token balance
* `total_supply` — Check the total supply (= total funded amount)
* `burn` — Redeem tokens (used internally during withdrawals)

## Earning Yield

### How Yield Accumulates

As the borrower makes repayments, a portion (based on the split ratio) flows into the **EMI pool**. This pool holds the yield available to investors.

### Claiming Yield

Call `claim_yield(investor)` to claim your share:

$$
\text{claimable} = \frac{\text{your\_balance}}{\text{total\_supply}} \times \text{emi\_pool} - \text{already\_claimed}
$$

* You can claim at any time during the **Active** or **FullyRepaid** phases
* Claims are cumulative — you receive the difference between your total entitlement and what you've already claimed
* You don't need to claim after every payment — yield accumulates

### Example

| Parameter                         | Value          |
| --------------------------------- | -------------- |
| Vault principal                   | 100,000 USDC   |
| Your deposit                      | 25,000 USDC    |
| Your share                        | 25%            |
| Total EMI pool (after 6 payments) | 20,000 USDC    |
| Your entitlement                  | 5,000 USDC     |
| Already claimed                   | 2,000 USDC     |
| **Claimable now**                 | **3,000 USDC** |

## Monitoring Your Investment

Track your vault's performance using query functions:

| Function                         | What It Shows                           |
| -------------------------------- | --------------------------------------- |
| `get_config()`                   | Vault terms and current phase           |
| `get_investor_deposit(investor)` | Your deposit amount                     |
| `get_claimable(investor)`        | Available yield to claim                |
| `balance(account)`               | Your debt token balance                 |
| `get_payments_made()`            | How many payments the borrower has made |
| `get_outstanding()`              | Remaining principal                     |
| `get_next_due()`                 | Next payment due date                   |
| `get_missed_payments()`          | Consecutive missed payments             |
| `get_emi_pool()`                 | Total accumulated yield pool            |
| `get_late_fees()`                | Accumulated late fees                   |

## Risk Awareness

### What Protects You

* **Manager collateral** — The Pool Manager stakes their own capital, which is slashed on default
* **Delinquency tracking** — Managers with defaults are blocked from new vaults
* **Credit limits** — Cap each manager's total exposure
* **Grace period** — Time buffer before default (default 30 days)
* **Permissionless default trigger** — Anyone can trigger default, preventing collusion
* **Funding/approval deadlines** — Prevent indefinite capital lock-up

### What to Watch For

* **Missed payments** — Check `get_missed_payments()` regularly
* **Late fees** — Rising late fees signal borrower distress
* **Phase changes** — Monitor for transitions to Renegotiation or Defaulted

{% hint style="danger" %}
**Lending carries risk.** If the borrower defaults, the Pool Manager's collateral provides partial recovery, but it may not cover your full deposit. Evaluate each vault's terms and Pool Manager track record carefully before investing.
{% endhint %}


# Smart Contract Architecture

Trilobyte is composed of five smart contracts deployed on Stellar's Soroban platform, built with OpenZeppelin Stellar Contracts for battle-tested security.

## Contract Diagram

```
┌───────────────────────────────────────────────┐
│                  Timelock                      │
│        (OZ TimelockController)                 │
│   Proposer / Executor / Canceller roles        │
│   Admin of Globals (governor actions delayed)  │
└──────────────────────┬────────────────────────┘
                       │ admin
┌──────────────────────▼────────────────────────┐
│                   Globals                      │
│        (OZ Access + Pausable + Upgradeable)    │
│   Settings · Roles · Fees · Assets · Managers  │
│   Pool-Manager Collateral (deposit/lock/slash) │
│   Factory + Vault registry                     │
└─────────┬─────────────────────────┬───────────┘
          │                         │
    ┌─────▼─────┐           ┌───────▼──────┐
    │  Factory   │──creates──►   Vault(s)   │
    │            │           │ (per-loan)   │
    │ Validates  │           │ SEP-41 Token │
    │ via Globals│           │ OZ Fungible  │
    └───────────┘           └──────┬───────┘
                                   │ lock / release / seize
                                   │ borrower-posted collateral
                            ┌──────▼─────────┐
                            │ CollateralEscrow│
                            │ (RWA pledges)   │
                            └─────────────────┘
```

## Contracts Overview

| Contract             | Purpose                                                                                                                                         | Key OZ Modules                                       |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| **Globals**          | Protocol settings, roles, fees, asset whitelist, manager collateral, manager credit/delinquency tracking, Factory & vault registry              | Access Control, Pausable, Upgradeable                |
| **Factory**          | Deploys and registers vaults, validates parameters against Globals                                                                              | Access Control, Upgradeable                          |
| **Vault**            | Per-loan contract — lifecycle, payments, yield, debt tokens, collateral release/slash/seize                                                     | FungibleToken, FungibleBurnable, Capped, Upgradeable |
| **Timelock**         | Governance delay on critical actions                                                                                                            | TimelockController                                   |
| **CollateralEscrow** | Custodies borrower-posted collateral (RWA certificates, fungible tokens) per loan; `lock` / `release` / `seize` driven by the controlling vault | —                                                    |

## Two Collateral Systems

Trilobyte has **two independent collateral mechanisms**:

1. **Pool-manager (skin-in-the-game) collateral** — lives in **Globals**. The pool manager who underwrites a vault stakes collateral; the Factory locks `principal × collateral_ratio / 100` on vault creation, the Vault releases it on full repayment and slashes it to investors on default.
2. **Borrower-posted collateral** — lives in **CollateralEscrow**. Optional per loan. The borrower pledges an asset (e.g. an RWA certificate) naming the vault as controller; the Vault verifies the pledge at disbursement, releases it to the borrower on full repayment, and seizes it to a recovery party on default.

## OpenZeppelin Stellar Contracts (v0.7.2)

| OZ Module             | Crate                    | Used In                 | Purpose                                                                                                               |
| --------------------- | ------------------------ | ----------------------- | --------------------------------------------------------------------------------------------------------------------- |
| Access Control (RBAC) | `stellar-access`         | Globals, Factory, Vault | `#[only_admin]`, `#[only_role(caller, "sec_adm")]`, `#[only_role(caller, "ops_adm")]` macros; two-step admin transfer |
| Pausable              | `stellar-contract-utils` | Globals                 | `#[when_not_paused]` macro                                                                                            |
| Upgradeable           | `stellar-contract-utils` | Globals, Factory, Vault | `#[derive(Upgradeable)]` with admin-authorised upgrades                                                               |
| FungibleToken         | `stellar-tokens`         | Vault                   | SEP-41 debt token with `FungibleBurnable` + `Capped`                                                                  |
| Timelock Controller   | `stellar-governance`     | Timelock                | Operation lifecycle (Unset → Waiting → Ready → Done)                                                                  |

## Tech Stack

| Component            | Technology                                                                               |
| -------------------- | ---------------------------------------------------------------------------------------- |
| Smart Contracts      | Rust + [Soroban SDK v26.1.0](https://soroban.stellar.org/)                               |
| Security             | [OpenZeppelin Stellar Contracts v0.7.2](https://docs.openzeppelin.com/stellar-contracts) |
| Blockchain           | Stellar (Soroban smart contract platform)                                                |
| WASM Target          | `wasm32v1-none`                                                                          |
| Deployment           | TypeScript + `@stellar/stellar-sdk` (drives the `stellar` CLI)                           |
| Day-Count Convention | 30/360 (standard in traditional finance)                                                 |
| Token Precision      | 7 decimal places (Stellar standard)                                                      |
| EMI Math             | Fixed-point integer arithmetic (10¹² internal precision, no floats)                      |

## Test Suite

140 tests across 5 packages:

| Package           | Tests | Coverage                                                                                                                                                                                    |
| ----------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Globals           | 29    | Core settings, roles, collateral, delinquency tracking, credit limits                                                                                                                       |
| Vault             | 83    | 64 contract + 19 math tests — lifecycle, deposits, withdrawals, payments, yield, debt tokens, risk/default, renegotiation, manager + borrower collateral, protocol fees, allowlist, upgrade |
| Timelock          | 14    | Lifecycle, scheduling, execution, cancellation, Globals integration                                                                                                                         |
| Collateral Escrow | 11    | Lock / release / seize, clawback handling, controller auth                                                                                                                                  |
| Factory           | 3     | Vault deployment, validation, registry                                                                                                                                                      |

## Cargo Dependencies

```toml
[workspace.dependencies]
soroban-sdk = "26.1.0"
stellar-access = "=0.7.2"
stellar-contract-utils = "=0.7.2"
stellar-governance = "=0.7.2"
stellar-macros = "=0.7.2"
stellar-tokens = "=0.7.2"
```


# Globals Contract

Protocol-wide settings, role management, fee configuration, pool-manager collateral, manager credit/delinquency tracking, and the Factory + vault registry.

**Source:** `contracts/globals/`

## Source Modules

| File           | Purpose                                                                                               |
| -------------- | ----------------------------------------------------------------------------------------------------- |
| `contract.rs`  | All public entry points — settings, roles, collateral, delinquency/credit, Factory/vault registration |
| `storage.rs`   | Data types (`GlobalSettings`, `Manager`, `ManagerCollateral`), storage keys, getters/setters          |
| `constants.rs` | Default values (`PROTOCOL_FEE = 50_000` = 0.5% in 7-decimal)                                          |
| `errors.rs`    | `#[contracterror]` enum                                                                               |
| `events.rs`    | `#[contractevent]` structs                                                                            |
| `test.rs`      | 29 tests (core settings + collateral + delinquency/credit)                                            |

## Roles & Authorisation

Roles use OpenZeppelin Access Control. The **admin** is the `governor` set at construction (later transferred to the Timelock). `sec_adm` and `ops_adm` are granted at construction.

| Role           | Key         | Who                 | Can Do                                                                                                                                                        |
| -------------- | ----------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Admin          | (OZ admin)  | Governor / Timelock | `whitelist_asset`, `remove_asset`, `set_treasury_address`, `set_factory`, `register_vault`\*, `upgrade`, two-step admin transfer                              |
| Security Admin | `"sec_adm"` | Security team       | `pause` / `unpause`                                                                                                                                           |
| Ops Admin      | `"ops_adm"` | Operations team     | `approve_pool_manager`, `remove_pool_manager`, `set_protocol_fee`, `update_settings`, `set_manager_inactive`, `set_manager_credit_limit`, `clear_delinquency` |

\* `register_vault` is admin-defined but additionally requires the call to come from the registered **Factory** (it re-checks `factory.require_auth()`).

{% hint style="info" %}
`whitelist_asset` / `remove_asset` / `set_treasury_address` / `set_factory` use the `#[only_admin]` macro. The pool-manager, fee, settings, and delinquency functions use `#[only_role(caller, "ops_adm")]`. `pause` / `unpause` use `#[only_role(caller, "sec_adm")]`.
{% endhint %}

## GlobalSettings

The settings struct has **21 fields** (no addresses or paused state — those live in OZ storage):

```rust
GlobalSettings {
    protocol_fee: i128,             // Fee (7-decimal, 50_000 = 0.5%)
    min_interest_rate: u32,         // Min annual rate, basis points
    max_interest_rate: u32,         // Max annual rate, basis points
    min_loan_term: u32,             // Min loan term, months
    max_loan_term: u32,             // Max loan term, months
    default_grace_period: u32,      // Default grace period (days)
    default_risk_tolerance: u32,    // Default-after % missed payments
    default_collateral_ratio: u32,  // Manager collateral as % of principal
    min_collateral_value: i128,     // Min collateral value (7-decimal)
    one_day: u64,                   // Seconds in a day
    one_year: u64,                  // Seconds in a 360-day year
    days_in_year_30_360: u32,       // 360
    days_in_month_30_360: u32,      // 30
    monthly_emi_interval: u64,      // EMI interval (seconds)
    default_penalty_rate_bps: u32,  // Penalty rate (1800 = 18% p.a.)
    min_emi_tolerance: u32,         // Min % of EMI to avoid penalty
    token_decimals: u32,            // 7
    min_loan_amount: i128,          // Minimum loan amount (7-decimal)
    max_loan_amount: i128,          // Maximum loan amount (7-decimal)
    default_timelock_delay: u64,    // Default timelock delay (ledgers)
    default_timelock_duration: u64, // Default timelock duration (ledgers)
}
```

{% hint style="warning" %}
The min/max loan bounds are named `min_loan_amount` / `max_loan_amount` (not `min_principal` / `max_principal`). Timelock delay/duration are measured in **ledgers** (\~5 s/ledger), not seconds.
{% endhint %}

## Functions

### Admin-only (`#[only_admin]`)

| Function                             | Description                                                             |
| ------------------------------------ | ----------------------------------------------------------------------- |
| `set_treasury_address(new_treasury)` | Update the treasury address (the new treasury must also `require_auth`) |
| `whitelist_asset(asset)`             | Whitelist a lending/collateral token                                    |
| `remove_asset(asset)`                | Remove a token from the whitelist                                       |
| `set_factory(factory)`               | Register the Factory address (required before vault creation works)     |
| `register_vault(vault)`              | Mark a vault as trusted (Factory-gated)                                 |
| `upgrade(new_wasm_hash, operator)`   | Upgrade the contract (auth via stored admin)                            |

### Two-step admin transfer (OZ Access Control)

| Function                                                                                                                                                                                            | Description                                                            |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| `transfer_admin_role(new_admin, live_until_ledger)`                                                                                                                                                 | Initiate admin transfer; pending until accepted or `live_until_ledger` |
| `accept_admin_transfer()`                                                                                                                                                                           | New admin accepts the pending transfer                                 |
| `renounce_admin()`                                                                                                                                                                                  | Current admin renounces                                                |
| Plus OZ role functions: `grant_role`, `revoke_role`, `renounce_role`, `has_role`, `get_role_admin`, `set_role_admin`, `get_role_member`, `get_role_member_count`, `get_admin`, `get_existing_roles` |                                                                        |

### Security Admin (`#[only_role(caller, "sec_adm")]`)

| Function          | Description                              |
| ----------------- | ---------------------------------------- |
| `pause(caller)`   | Pause all `#[when_not_paused]` functions |
| `unpause(caller)` | Resume normal operation                  |

### Operations Admin (`#[only_role(caller, "ops_adm")]`)

| Function                                                | Description                                                   |
| ------------------------------------------------------- | ------------------------------------------------------------- |
| `approve_pool_manager(manager, caller, max_credit)`     | Approve a manager with a credit limit (returns the `Manager`) |
| `remove_pool_manager(manager_address, caller)`          | Remove a manager                                              |
| `set_protocol_fee(fee, caller)`                         | Update the protocol fee                                       |
| `update_settings(settings, caller)`                     | Replace the full `GlobalSettings` struct                      |
| `set_manager_inactive(manager_address, caller)`         | Deactivate a manager                                          |
| `set_manager_credit_limit(manager, max_credit, caller)` | Update a manager's credit limit                               |
| `clear_delinquency(manager, caller)`                    | Clear a manager's delinquency record after review             |

### Collateral (manager / cross-contract, `#[when_not_paused]`)

| Function                                               | Auth             | Description                                                                            |
| ------------------------------------------------------ | ---------------- | -------------------------------------------------------------------------------------- |
| `deposit_collateral(manager, token, amount)`           | Manager          | Stake collateral (manager must be active, token whitelisted, single token per manager) |
| `withdraw_collateral(manager, amount)`                 | Manager          | Withdraw unlocked collateral (`staked − locked`)                                       |
| `lock_collateral(manager, amount)`                     | Factory          | Lock collateral on vault creation                                                      |
| `release_collateral(caller, manager, amount)`          | Registered Vault | Release on full repayment / cancellation                                               |
| `slash_collateral(caller, manager, amount, recipient)` | Registered Vault | Slash on default, transfer to `recipient`                                              |
| `get_manager_collateral(manager)`                      | Any              | View `{ token, staked, locked }`                                                       |
| `get_available_collateral(manager)`                    | Any              | View `staked − locked`                                                                 |

### Manager credit & delinquency tracking (cross-contract)

| Function                                         | Called By | Description                                       |
| ------------------------------------------------ | --------- | ------------------------------------------------- |
| `mark_delinquent(caller, manager)`               | Vault     | Increment delinquency count on default            |
| `clear_delinquency(manager, caller)`             | Ops Admin | Reset delinquency count                           |
| `increment_outstanding(manager, amount)`         | Factory   | Track new vault principal against credit          |
| `decrement_outstanding(caller, manager, amount)` | Vault     | Release principal on repayment / default / expiry |
| `is_delinquent(manager)`                         | Any       | Whether the manager has any uncleared default     |
| `get_manager(manager)`                           | Any       | Full `Manager` record (`Option`)                  |

### Public Queries

| Function                 | Returns                |
| ------------------------ | ---------------------- |
| `get_global_settings()`  | `GlobalSettings`       |
| `get_treasury_address()` | Treasury address       |
| `get_supported_assets()` | Whitelisted token list |
| `list_pool_managers()`   | `Vec<Manager>`         |
| `paused()`               | `bool`                 |

## Events

| Event                       | Key Fields                                   | When                                     |
| --------------------------- | -------------------------------------------- | ---------------------------------------- |
| `RoleUpdated`               | `role`, `new_address`                        | Treasury (or other role address) updated |
| `ProtocolPaused`            | `paused`                                     | Paused / unpaused                        |
| `ManagerApproved`           | `manager`                                    | Manager approved                         |
| `ManagerRemoved`            | `manager`                                    | Manager removed                          |
| `AssetWhitelisted`          | `asset`                                      | Asset added                              |
| `AssetRemoved`              | `asset`                                      | Asset removed                            |
| `FeeUpdated`                | `new_fee`                                    | Protocol fee updated                     |
| `CollateralDeposited`       | `manager`, `token`, `amount`, `total_staked` | Manager stakes collateral                |
| `CollateralWithdrawn`       | `manager`, `amount`, `total_staked`          | Manager withdraws collateral             |
| `CollateralLocked`          | `manager`, `amount`, `total_locked`          | Collateral locked for a vault            |
| `CollateralReleased`        | `manager`, `amount`, `total_locked`          | Collateral released                      |
| `CollateralSlashed`         | `manager`, `amount`, `remaining_staked`      | Collateral slashed on default            |
| `ManagerMarkedDelinquent`   | `manager`, `delinquency_count`               | Vault defaulted                          |
| `ManagerDelinquencyCleared` | `manager`                                    | Delinquency cleared                      |
| `ManagerCreditLimitSet`     | `manager`, `max_credit`                      | Credit limit set/updated                 |
| `ManagerOutstandingUpdated` | `manager`, `outstanding_principal`           | Outstanding principal changed            |

## Storage Design

`GlobalSettings`, treasury, the Factory address, supported assets, pool managers, and per-manager collateral are stored in **instance storage** with TTL extension (\~30-day threshold, \~31-day bump). Registered vaults are stored in **persistent storage** keyed by vault address, with a TTL clamped to the network maximum (`max_ttl()`).


# Factory Contract

Deploys and registers vault contracts. Validates all parameters against the Globals contract before deployment.

**Source:** `contracts/factory/`

## Source Modules

| File          | Purpose                                                                                                  |
| ------------- | -------------------------------------------------------------------------------------------------------- |
| `contract.rs` | Constructor, `set_vault_wasm_hash`, `create_vault`, queries, inline `GlobalsClient` and mirrored structs |
| `storage.rs`  | Data keys (globals, wasm\_hash, 3-index vault registry), getters/setters, pagination                     |
| `errors.rs`   | `#[contracterror]` enum (11 variants)                                                                    |
| `events.rs`   | `VaultDeployed`, `WasmHashUpdated` events                                                                |
| `test.rs`     | 3 tests                                                                                                  |

## How It Works

1. Governor deploys the Factory with a Globals address and an initial vault WASM hash.
2. When a Pool Manager calls `create_vault(params, salt)`, the factory:
   * Validates the manager is approved and active (via `GlobalsClient::list_pool_managers`)
   * Checks the manager is **not delinquent** (`is_delinquent` — blocked if any vault defaulted)
   * Checks the manager's **credit limit** (`outstanding + principal ≤ max_credit`)
   * Validates the lending token is whitelisted (`get_supported_assets`)
   * Validates interest rate, loan term, principal, and split ratio against `get_global_settings`
   * Checks the manager has enough **available collateral** (`principal × default_collateral_ratio / 100`)
   * **Locks manager collateral** via `GlobalsClient::lock_collateral`
   * **Increments outstanding principal** via `GlobalsClient::increment_outstanding`
   * Deploys a new vault using `e.deployer().with_current_contract(salt).deploy_v2(wasm_hash, (globals, params))`
   * **Registers the vault in Globals** via `GlobalsClient::register_vault` (so the vault is authorised for cross-contract calls), then in three Factory indexes: all vaults, per-manager, per-borrower
3. The governor can update the vault WASM hash via `set_vault_wasm_hash` (for future vault template upgrades).

{% hint style="warning" %}
`create_vault` calls back into Globals (`lock_collateral`, `increment_outstanding`, `register_vault`). These require the Factory to be registered in Globals via `set_factory` first — otherwise the calls fail. See [Deployment](/technical-documentation/deployment).
{% endhint %}

## Functions

### Constructor

```
__constructor(governor, globals, vault_wasm_hash)
```

Initialise the factory with admin (`governor`), Globals reference, and the vault template hash.

### Admin Actions (`#[only_admin]`)

| Function                           | Description                                 |
| ---------------------------------- | ------------------------------------------- |
| `set_vault_wasm_hash(new_hash)`    | Update the vault WASM template              |
| `upgrade(new_wasm_hash, operator)` | Upgrade the Factory (auth via stored admin) |

### Vault Creation

| Function                                  | Description                                                          |
| ----------------------------------------- | -------------------------------------------------------------------- |
| `create_vault(params: VaultParams, salt)` | Deploy a new vault with full validation; returns the vault `Address` |

**VaultParams (15 fields):**

```rust
VaultParams {
    manager: Address,
    borrower: Address,
    lent_token: Address,
    principal: i128,
    interest_rate: u32,
    loan_term: u32,
    split_ratio: u32,
    permissioned: bool,
    grace_period: u64,
    funding_deadline: u64,
    approval_deadline: u64,
    collateral_escrow: Option<Address>,   // None = manager-collateral-only loan
    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
}
```

{% hint style="info" %}
The four borrower-collateral fields are optional in effect: leave `collateral_escrow` as `None` (and the others empty / `0`) for a manager-collateral-only loan. If `collateral_escrow` is `Some`, the vault constructor asserts `collateral_token`, `collateral_recovery`, and a positive `collateral_min` are all present.
{% endhint %}

### Queries

| Function                                           | Returns                                             |
| -------------------------------------------------- | --------------------------------------------------- |
| `get_globals()`                                    | Globals contract address                            |
| `get_vault_wasm_hash()`                            | Current vault template hash                         |
| `get_vault_count()`                                | Total deployed vaults (O(1))                        |
| `get_all_vaults()`                                 | First 100 vault addresses (use pagination for more) |
| `get_vaults_page(start, limit)`                    | A page of vault addresses                           |
| `get_manager_vaults(manager)`                      | First 100 vaults for a manager                      |
| `get_manager_vaults_page(manager, start, limit)`   | A page of a manager's vaults                        |
| `get_manager_vault_count(manager)`                 | Number of vaults for a manager                      |
| `get_borrower_vaults(borrower)`                    | First 100 vaults for a borrower                     |
| `get_borrower_vaults_page(borrower, start, limit)` | A page of a borrower's vaults                       |
| `get_borrower_vault_count(borrower)`               | Number of vaults for a borrower                     |

{% hint style="info" %}
The registry is stored as indexed persistent entries with an O(1) count, avoiding Soroban's object-size limit. `get_all_vaults` / `get_manager_vaults` / `get_borrower_vaults` cap at **100**; use the `_page` variants for larger sets.
{% endhint %}

## Validation Checks

The Factory performs extensive validation before deploying a vault. Error codes are from the **Factory** error enum (`contracts/factory/src/errors.rs`):

| Check             | Error Code                   | Description                                   |
| ----------------- | ---------------------------- | --------------------------------------------- |
| Manager approved  | `ManagerNotApproved (1)`     | Manager must be active in Globals             |
| Asset whitelisted | `AssetNotSupported (2)`      | Token must be whitelisted in Globals          |
| Interest rate     | `InvalidRate (3)`            | Must be within global min/max                 |
| Loan term         | `InvalidTerm (4)`            | Must be within global min/max                 |
| Principal         | `InvalidPrincipal (5)`       | Must be `> 0` and within global min/max       |
| Split ratio       | `InvalidSplitRatio (6)`      | Must be 1–99                                  |
| WASM hash set     | `WasmHashNotSet (7)`         | Vault template hash must be configured        |
| Authorised        | `Unauthorized (8)`           | Caller not authorised                         |
| Collateral        | `InsufficientCollateral (9)` | Manager must have enough available collateral |
| Not delinquent    | `ManagerDelinquent (10)`     | Manager must have no uncleared defaults       |
| Credit limit      | `CreditLimitExceeded (11)`   | `outstanding + principal ≤ max_credit`        |

## Events

| Event             | Key Fields                                                                | When             |
| ----------------- | ------------------------------------------------------------------------- | ---------------- |
| `VaultDeployed`   | `vault`, `manager`, `borrower`, `principal`, `loan_term`, `interest_rate` | Vault created    |
| `WasmHashUpdated` | `old_hash`, `new_hash`                                                    | Template updated |


# 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).
{% 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 %}


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

**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.


# Timelock Contract

OpenZeppelin Timelock Controller (Stellar Contracts v0.7.2) for governance delay on critical protocol actions.

**Source:** `contracts/timelock/`

## Overview

The Timelock enforces a **minimum delay** on governance operations. Critical protocol changes (fee updates, asset whitelisting, contract upgrades) must be scheduled, wait for the delay to pass, and then be executed. This gives protocol participants time to review and react.

{% hint style="warning" %}
**Delays are measured in LEDGERS, not seconds.** OZ Timelock v0.7 measures `min_delay` and per-operation `delay` in ledger-sequence units (\~5 s/ledger). A 1-day delay is therefore \~17 280 ledgers, not `86400`.
{% endhint %}

## Operation Lifecycle

```
Unset → Waiting → Ready → Done
```

| State       | Description                                           |
| ----------- | ----------------------------------------------------- |
| **Unset**   | Operation doesn't exist yet                           |
| **Waiting** | Scheduled, waiting for the delay (in ledgers) to pass |
| **Ready**   | Delay has passed, can be executed                     |
| **Done**    | Successfully executed                                 |

## Roles

| Role          | Description                                                                                                                |
| ------------- | -------------------------------------------------------------------------------------------------------------------------- |
| **Proposer**  | Can schedule new operations with a delay. **Proposers are also automatically granted the Canceller role** at construction. |
| **Executor**  | Can execute operations once they are ready. **If no executors are configured, anyone can execute** ready operations.       |
| **Canceller** | Can cancel pending operations before execution                                                                             |
| **Admin**     | Can manage roles and update the minimum delay. By default the contract is its own admin (self-administered).               |

## Functions

| Function                                                                                                 | Role       | Description                                                                                              |
| -------------------------------------------------------------------------------------------------------- | ---------- | -------------------------------------------------------------------------------------------------------- |
| `schedule_op(target, function, args, predecessor, salt, delay, proposer)`                                | Proposer   | Schedule an operation; returns the operation id (hash). `delay` is in ledgers and must be ≥ `min_delay`. |
| `execute_op(target, function, args, predecessor, salt, executor)`                                        | Executor\* | Execute a ready operation (external targets). \*Anyone if no executors configured.                       |
| `cancel_op(operation_id, canceller)`                                                                     | Canceller  | Cancel a pending operation.                                                                              |
| `update_delay(new_delay)`                                                                                | Admin      | Update the minimum delay (self-administered → goes through the timelock).                                |
| `get_min_delay()`                                                                                        | —          | Current minimum delay (ledgers).                                                                         |
| `hash_operation(...)`                                                                                    | —          | Compute an operation's id.                                                                               |
| `get_operation_state(id)`                                                                                | —          | Returns the `OperationState`.                                                                            |
| `operation_exists(id)` / `is_operation_pending(id)` / `is_operation_ready(id)` / `is_operation_done(id)` | —          | State predicates.                                                                                        |

The Timelock also exposes the OZ `AccessControl` interface (`grant_role`, `revoke_role`, `has_role`, …).

## Integration with Globals

The Timelock is set as the **admin** of the Globals contract during deployment (via the two-step `transfer_admin_role` / `accept_admin_transfer`). This means:

* All admin-gated functions on Globals (`whitelist_asset`, `set_factory`, `upgrade`, etc.) require Timelock authorisation
* To change such a parameter, a Proposer schedules the operation, waits for the delay (in ledgers), then an Executor executes it
* This prevents instant, unilateral changes to critical protocol parameters

{% hint style="info" %}
`ops_adm`-gated functions on Globals (fees, settings, pool-manager approval, delinquency) are **not** routed through the Timelock — they are gated by the operations-admin role directly.
{% endhint %}

## Self-Administered

The Timelock is deployed **self-administered** (admin `None` → the contract is its own admin):

* No single external address can bypass the time delay
* The Timelock's own settings (minimum delay, roles) can only be changed through the Timelock itself, via the `CustomAccountInterface` self-admin path
* This creates a fully decentralised governance loop

## Deployment

```
__constructor(min_delay, proposers, executors, admin: None)
```

* `min_delay` — Minimum number of **ledgers** between scheduling and execution
* `proposers` — Addresses that can schedule operations (also granted the canceller role)
* `executors` — Addresses that can execute ready operations (empty ⇒ anyone can execute)
* `admin: None` — Self-administered (no external admin)

{% hint style="warning" %}
Upgrading the timelock WASM changes the operation storage key, so any operations scheduled under a prior version are dropped. Drain in-flight operations before upgrading a live timelock.
{% endhint %}

## Test Coverage

14 tests covering:

* Operation lifecycle (schedule → ready → execute)
* Cancellation before execution
* Delay enforcement (in ledgers)
* Role-based access control (incl. proposer-auto-canceller and open-execution-when-no-executors)
* Integration with the Globals contract (scheduling admin-gated operations)


# Deployment

Trilobyte contracts are deployed to Stellar with a TypeScript script that drives the `stellar` CLI (`@stellar/stellar-sdk` is used for keypair/address handling).

## Full Protocol Deployment

The main deployment script is `scripts/deploy-protocol.ts`. It deploys the four governance/registry contracts and runs post-deploy initialisation. (The **CollateralEscrow** is deployed separately, per RWA-collateral lender — it is not part of the core protocol deploy.)

### Step 1 — Deploy Globals

```
Globals(governor, treasury, security_admin, operations_admin)
```

Globals is deployed first. `governor` becomes the OZ admin; `security_admin` and `operations_admin` are granted their roles in the constructor.

### Step 2 — Deploy Timelock

```
Timelock(min_delay, proposers, executors, admin: None)
```

Self-administered. `min_delay` is in **ledgers**. Proposers are also granted the canceller role; an empty executor list means anyone can execute.

### Step 3 — Upload Vault WASM

The compiled vault WASM is uploaded (`stellar contract upload`), returning a WASM hash used by the Factory to deploy vault instances.

### Step 4 — Deploy Factory

```
Factory(governor, globals_address, vault_wasm_hash)
```

### Step 5 — Register the Factory in Globals (MANDATORY)

```
globals.set_factory(factory_address)
```

{% hint style="danger" %}
**This step is required.** Globals gates `lock_collateral`, `increment_outstanding`, and `register_vault` on the call coming from the registered Factory. Until `set_factory` has been called, `create_vault` fails when it tries those cross-contract calls — no vault can be created. `set_factory` is `#[only_admin]`, so it must run **before** admin is transferred to the Timelock.
{% endhint %}

### Step 6 — Whitelist Assets

```
globals.whitelist_asset(asset)
```

{% hint style="info" %}
Must be done **before** transferring Globals admin to the Timelock, since it requires direct admin access.
{% endhint %}

### Step 7 — Approve Initial Managers

```
globals.approve_pool_manager(manager_address, caller, max_credit)
```

A single call both registers and approves a manager (there is no separate `add_pool_manager`). `caller` must hold the `ops_adm` role.

### Step 8 — Transfer Globals Admin to the Timelock

Globals uses the OZ **two-step** admin transfer:

```
globals.transfer_admin_role(timelock_address, live_until_ledger)   // current admin initiates
globals.accept_admin_transfer()                                     // the Timelock accepts
```

`live_until_ledger` bounds when the pending transfer expires (`0` cancels it). After acceptance, all admin-gated operations on Globals must go through the Timelock's governance process.

{% hint style="warning" %}
**This step is effectively irreversible.** Once admin is the Timelock, direct admin access is gone. Ensure `set_factory`, asset whitelisting, and manager approval are all complete first.
{% endhint %}

{% hint style="danger" %}
**Deploy-script discrepancy (verify before running).** As of this writing `scripts/deploy-protocol.ts` calls a single `transfer_admin` (one-step) for step 8. The deployed Globals contract exposes only the OZ two-step `transfer_admin_role` / `accept_admin_transfer` — there is no `transfer_admin` function. The single call will fail. Use `transfer_admin_role(new_admin, live_until_ledger)` followed by `accept_admin_transfer()`.
{% endhint %}

## Running the Deployment

```bash
# Build all contracts (WASM target wasm32v1-none)
stellar contract build

# Deploy the full protocol
npx ts-node scripts/deploy-protocol.ts
```

## Configuration

All deployment parameters are configured via environment variables. See `.env.example`:

| Variable                   | Description                                               |
| -------------------------- | --------------------------------------------------------- |
| `SOROBAN_NETWORK`          | Network to deploy to (`testnet` / `mainnet`)              |
| `NETWORK_RPC_URL`          | Soroban RPC endpoint                                      |
| `NETWORK_PASSPHRASE`       | Network passphrase                                        |
| `NETWORK_HORIZON_URL`      | Horizon endpoint                                          |
| `WASM_TARGET_DIR`          | Build output dir (default `target/wasm32v1-none/release`) |
| `SOURCE_SECRET_KEY`        | Secret key for the deploying account                      |
| `GOVERNOR_ADDRESS`         | Initial governor (root admin of Globals)                  |
| `TREASURY_ADDRESS`         | Protocol treasury address                                 |
| `SECURITY_ADMIN_ADDRESS`   | Security admin address                                    |
| `OPERATIONS_ADMIN_ADDRESS` | Operations admin address                                  |
| `TIMELOCK_MIN_DELAY`       | Minimum delay for timelocked operations (ledgers)         |
| `TIMELOCK_PROPOSERS`       | Comma-separated proposer addresses (≥ 1 required)         |
| `TIMELOCK_EXECUTORS`       | Comma-separated executor addresses (empty ⇒ anyone)       |
| `WHITELIST_ASSETS`         | Comma-separated asset addresses to whitelist              |
| `INITIAL_MANAGERS`         | Comma-separated manager addresses to approve              |
| `MANAGER_CREDIT_LIMIT`     | Credit limit applied to initial managers (7-decimal)      |
| `STELLAR_CLI_PATH`         | (optional) Absolute path override for the `stellar` CLI   |

{% hint style="info" %}
The deployer secret is passed to the CLI via the `STELLAR_ACCOUNT` / `STELLAR_SIGN_WITH_KEY` environment variables (never on argv), and the `stellar` CLI path is resolved on a sanitised PATH to prevent PATH-hijack.
{% endhint %}

## Output

Deployment results are saved to `.soroban/contract-ids.json`:

```json
{
  "network": "testnet",
  "deployedAt": "2026-02-27T12:00:00Z",
  "contracts": {
    "globals": "CABC...",
    "timelock": "CDEF...",
    "factory": "CGHI..."
  },
  "vaultWasmHash": "abc123..."
}
```


# TypeScript SDK

Trilobyte provides auto-generated TypeScript client bindings for all **five** contracts. These enable frontend applications, backend services, and keeper bots to interact with the protocol.

## Packages

| Package                               | Path                                    | Description                                                                |
| ------------------------------------- | --------------------------------------- | -------------------------------------------------------------------------- |
| `@trilobyte/globals-client`           | `contracts/globals/bindings/`           | Settings, roles, collateral, fees, asset whitelist, Factory/vault registry |
| `@trilobyte/vault-client`             | `contracts/vault/bindings/`             | Full vault lifecycle, payments, yield, debt tokens, SEP-41                 |
| `@trilobyte/factory-client`           | `contracts/factory/bindings/`           | Vault creation, registry queries (incl. pagination)                        |
| `@trilobyte/timelock-client`          | `contracts/timelock/bindings/`          | Schedule, execute, cancel operations, AccessControl                        |
| `@trilobyte/collateral-escrow-client` | `contracts/collateral-escrow/bindings/` | Borrower-collateral lock / release / seize, pledge queries                 |

## Generation

Bindings are generated using the Stellar CLI:

```bash
# Generate all bindings (build WASM + generate + compile)
./scripts/generate-bindings.sh
```

This runs `stellar contract bindings typescript` for each contract, generating fully typed TypeScript clients from the contract ABIs.

## Usage

### Installation

Add bindings as local path dependencies in your project's `package.json`:

```json
{
  "dependencies": {
    "@trilobyte/globals-client": "file:../trilobyte-new/contracts/globals/bindings",
    "@trilobyte/vault-client": "file:../trilobyte-new/contracts/vault/bindings",
    "@trilobyte/factory-client": "file:../trilobyte-new/contracts/factory/bindings",
    "@trilobyte/timelock-client": "file:../trilobyte-new/contracts/timelock/bindings",
    "@trilobyte/collateral-escrow-client": "file:../trilobyte-new/contracts/collateral-escrow/bindings"
  }
}
```

### Example: Query Vault Config

```typescript
import { Client as VaultClient } from '@trilobyte/vault-client';

const vault = new VaultClient({
  contractId: 'CABC...',
  networkPassphrase: 'Test SDF Network ; September 2015',
  rpcUrl: 'https://soroban-testnet.stellar.org',
});

const config = await vault.get_config();
console.log('Phase:', config.result.phase);

// `outstanding` is NOT a field on VaultConfig — query it separately:
const outstanding = await vault.get_outstanding();
console.log('Outstanding:', outstanding.result);
```

### Example: Deposit into a Vault

```typescript
const tx = await vault.deposit({
  investor: 'GABC...',
  amount: 100_000_0000000n, // 100,000 with 7 decimals
});

await tx.signAndSend();
```

### Example: Create a Vault

`create_vault` takes the full 15-field `VaultParams`. For a manager-collateral-only loan, leave `collateral_escrow` (and the related fields) empty:

```typescript
import { Client as FactoryClient } from '@trilobyte/factory-client';

const factory = new FactoryClient({
  contractId: 'CDEF...',
  networkPassphrase: 'Test SDF Network ; September 2015',
  rpcUrl: 'https://soroban-testnet.stellar.org',
});

const tx = await factory.create_vault({
  params: {
    manager: 'GABC...',
    borrower: 'GDEF...',
    lent_token: 'CGHI...',
    principal: 500_000_0000000n,
    interest_rate: 1000, // 10%
    loan_term: 12,
    split_ratio: 80,
    permissioned: false,
    grace_period: 2592000n,
    funding_deadline: 0n,
    approval_deadline: 0n,
    // Borrower collateral (optional). For a manager-collateral-only loan:
    collateral_escrow: undefined,   // Option<Address> → None
    collateral_token: undefined,    // Option<Address> → None
    collateral_recovery: undefined, // Option<Address> → None
    collateral_min: 0n,
  },
  salt: Buffer.from('unique-salt-value-32-bytes......'), // 32 bytes
});

await tx.signAndSend();
```

For a **borrower-collateral-backed** loan, set `collateral_escrow` to the escrow contract address, `collateral_token` to the pledged asset, `collateral_recovery` to the recovery party, and `collateral_min` to the on-chain floor — then have the borrower `lock` the pledge in the escrow and pass its `pledge_id` to `approve_and_disburse`.

## Keeper / Cron Integration

The TypeScript bindings are consumed by the `trilobyte-vault` NestJS backend. The keeper cron module uses them to:

1. **`apply_late_fee()`** — when a payment is overdue (manager-only)
2. **`check_default()`** — when past the grace period (permissionless)
3. **`check_funding_expiry()`** — for funding-deadline enforcement (permissionless)
4. **`check_approval_expiry()`** — for approval-deadline enforcement (permissionless)

All except `apply_late_fee` are permissionless entry points callable by any address.


# FAQ

## General

### What is Trilobyte?

Trilobyte is a decentralised lending protocol built on Stellar's Soroban smart contract platform. It connects businesses that need working capital with investors who earn yield from loan repayments.

### Is Trilobyte live?

Trilobyte is currently deployed on **Stellar Testnet** and undergoing auditing. Mainnet launch has not been announced yet.

### What blockchain does Trilobyte run on?

Trilobyte runs on **Stellar** using the **Soroban** smart contract platform. It leverages Stellar's low fees, fast finality, and SEP-41 token standard.

### Is the code open source?

Yes. The smart contracts are written in Rust and built with OpenZeppelin Stellar Contracts v0.6.0 for battle-tested security.

***

## For Investors

### How do I earn yield?

You earn yield by depositing funds into a Vault during its RaisingFunds phase. As the borrower makes repayments, a portion (determined by the split ratio) flows into the EMI pool. You can claim your pro-rata share at any time.

### What are debt tokens (tVLT)?

When you deposit into a vault, you receive Trilobyte Vault Tokens (tVLT) 1:1 with your **net** deposit (after the 0.5% fee). These are SEP-41 fungible tokens that represent your share of the vault and are **non-transferable**. Your yield entitlement is proportional to your token balance.

### Can I withdraw my deposit?

You can withdraw during the **RaisingFunds** phase before the vault is fully funded. Once the vault transitions to AwaitingApproval, your funds are committed.

### What happens if the borrower defaults?

The Pool Manager's collateral is slashed to provide partial recovery for investors. However, collateral may not cover your full deposit. Always evaluate the vault terms and manager track record before investing.

### Are there fees?

A **0.5% protocol fee** goes to the protocol treasury. It's taken from investor deposits, and on repayments from the **borrower's cash share only** — so it never reduces investor yield. Separately, a borrower who misses a payment can be charged an 18%/yr late fee.

***

## For Borrowers

### How do I get a loan?

Work with an approved Pool Manager who will evaluate your business, structure the loan terms, and create a vault on your behalf. The loan terms are negotiated off-chain and then encoded into a smart contract.

### What happens if I miss a payment?

The Pool Manager can apply an **18% p.a. late fee**. If you miss payments beyond the **grace period** (default 30 days), anyone can trigger default — which slashes the manager's collateral.

### Can I renegotiate my loan?

Yes. Your Pool Manager can propose new terms (interest rate and/or loan term). If approved, the EMI is recalculated on the remaining outstanding principal with a reset payment schedule.

### What is the cash pool?

Each repayment you make is split between the EMI pool (investor yield) and the cash pool (your operating capital). You can withdraw from the cash pool at any time during the Active or FullyRepaid phases.

***

## For Pool Managers

### How do I become a Pool Manager?

You must be approved by the protocol's Operations Admin. They register your address and assign a credit limit that caps your total outstanding principal.

### What is collateral and why do I need it?

You must deposit collateral before creating vaults. This is your "skin in the game" — it gets slashed if a vault defaults, providing partial recovery for investors. This aligns your incentives with investor protection.

### What happens if a vault defaults?

Your locked collateral for that vault is slashed, you are flagged as delinquent, and you are blocked from creating new vaults. Only an Operations Admin can clear your delinquency after review.

### What is a credit limit?

Your credit limit caps the total outstanding principal across all your active vaults. It's set by the Operations Admin and prevents over-leveraging.

***

## Technical

### What token standard does Trilobyte use?

Debt tokens follow the **SEP-41** standard — Stellar's native fungible token specification. The vault contract implements the `FungibleToken` and `FungibleBurnable` traits from OpenZeppelin.

### How is the EMI calculated?

Using the standard amortisation formula with integer-only math (Soroban has no floats). Internal precision uses 10¹² scaling, and the final EMI is ceiling-rounded.

### What is the day-count convention?

Trilobyte uses **30/360** — each month is treated as 30 days. This is standard in traditional finance.

### What security modules are used?

Trilobyte uses OpenZeppelin Stellar Contracts v0.6.0:

* **Access Control** — Role-based permissions
* **Pausable** — Emergency circuit breaker
* **Upgradeable** — Governor-authorised contract upgrades
* **FungibleToken** — SEP-41 debt tokens
* **TimelockController** — Governance delay


# Glossary

| Term                     | Definition                                                                                                                                             |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Amortisation**         | The process of reducing outstanding principal with each payment. Each EMI contains an interest component and a principal component.                    |
| **Allowlist**            | A list of approved investor addresses for permissioned vaults. Managed by the Pool Manager.                                                            |
| **Basis Points (bps)**   | A unit of measurement: 1 bps = 0.01%. An interest rate of 1000 bps = 10%.                                                                              |
| **Borrower**             | A business that receives a loan through a Trilobyte vault and repays through incoming revenue.                                                         |
| **Cash Pool**            | The portion of each repayment allocated to the borrower (based on `100 - split_ratio`). Withdrawable by the borrower.                                  |
| **Collateral**           | Tokens staked by Pool Managers as guarantee. Locked when a vault is created, released on repayment, slashed on default.                                |
| **Credit Limit**         | The maximum total outstanding principal a Pool Manager can have across all active vaults. Set by the Operations Admin.                                 |
| **Debt Token (tVLT)**    | Non-transferable SEP-41 fungible token minted 1:1 with investor deposits. Represents the investor's share of the vault.                                |
| **Default**              | Triggered when a borrower fails to repay within the grace period. Results in collateral slashing and delinquency flagging.                             |
| **Delinquency**          | A flag applied to Pool Managers whose vaults have defaulted. Blocks new vault creation until cleared by Operations Admin.                              |
| **EMI**                  | Equated Monthly Instalment — the fixed payment amount covering principal and interest, calculated using standard amortisation.                         |
| **EMI Pool**             | The portion of each repayment allocated to investors (based on `split_ratio`). Claimable pro-rata by investors.                                        |
| **Factory**              | Smart contract that deploys new vault instances. Validates parameters and manager eligibility before deployment.                                       |
| **Finalized**            | Terminal vault phase. All funds distributed or refunded. No further actions possible.                                                                  |
| **Globals**              | Smart contract managing protocol-wide settings, roles, fees, asset whitelist, and pool manager collateral.                                             |
| **Governor**             | The protocol administrator. In production, governance actions are routed through the Timelock for delay enforcement.                                   |
| **Grace Period**         | Configurable window (default 30 days) after a missed payment before default can be triggered.                                                          |
| **Investor**             | An address that deposits funds into a vault and earns yield from borrower repayments.                                                                  |
| **Late Fee**             | An 18% p.a. penalty applied by the Pool Manager when a payment is overdue.                                                                             |
| **Operations Admin**     | Protocol role responsible for approving/removing pool managers, setting credit limits, and clearing delinquency.                                       |
| **Permissioned Vault**   | A vault where only allowlisted addresses can deposit. Access controlled by the Pool Manager.                                                           |
| **Permissionless Vault** | A vault where any address can deposit.                                                                                                                 |
| **Pool Manager**         | A professional underwriter who evaluates borrowers, structures loans, stakes collateral, and manages vaults.                                           |
| **Principal**            | The total loan amount requested by the borrower and funded by investors.                                                                               |
| **Protocol Fee**         | A 0.5% fee sent to the protocol treasury — taken from investor deposits, and on repayments from the borrower's cash share only (never investor yield). |
| **Renegotiation**        | Process where the Pool Manager proposes new loan terms (rate + term). EMI is recalculated on outstanding principal.                                    |
| **Security Admin**       | Protocol role with emergency pause/unpause powers.                                                                                                     |
| **SEP-41**               | Stellar Ecosystem Proposal 41 — the fungible token standard on Stellar/Soroban.                                                                        |
| **Soroban**              | Stellar's smart contract platform, providing a Rust-based execution environment.                                                                       |
| **Split Ratio**          | The percentage of each repayment routed to the EMI pool (investor yield). The remainder goes to the cash pool.                                         |
| **Timelock**             | Smart contract enforcing a minimum delay on governance operations before they can be executed.                                                         |
| **Treasury**             | The protocol address that receives protocol fees from deposits and repayments.                                                                         |
| **Vault**                | A smart contract representing a single loan. Manages the full lifecycle from funding to repayment to settlement.                                       |
| **VaultPhase**           | The current state of a vault: RaisingFunds, AwaitingApproval, Active, FullyRepaid, Finalized, Renegotiation, or Defaulted.                             |
| **WASM Hash**            | The hash of the compiled vault contract bytecode, used by the Factory to deploy new vault instances.                                                   |


