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.
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.
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.
Source Modules
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
The record exists only while the pledge is active; it is deleted on release / seize.
Functions
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.
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.
How the Vault Uses It
Setup — the loan is created with
collateral_escrow,collateral_token,collateral_recovery, andcollateral_minset onVaultParams.Lock — the borrower calls
lock(...)naming the vault ascontroller, and passes the returnedpledge_idtoapprove_and_disburse(caller, Some(pledge_id)).Disbursement gate — the vault verifies the pledge backs this vault:
controller == vault,token == collateral_token,borrower == loan borrower,recovery_party == collateral_recovery, and bothpledge.amountand the liveavailable_collateralare ≥collateral_min. Any mismatch →CollateralPledgeInvalid.Repaid — on
FullyRepaidthe vault callsescrow.release(pledge_id)→ collateral back to the borrower.Defaulted — on
check_defaultthe vault callsescrow.seize(pledge_id)→ collateral to therecovery_partyfor off-chain disposition.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
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
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.
Last updated