Skip to main content

Collateral

Equillar supports an optional collateral path that can be used to settle positions when needed.

Adding collateral

Collateral is added through the function:

  • add_collateral(collateral_token_addr, collateral_token_amount, collateral_token_symbol, collateral_addr)

This collateral serves as a guarantee of payment in the event that the company fails to meet its payment obligations.

stellar contract invoke \
--id <YOUR_CONTRACT_ID> \
-s admin \
--network testnet \
-- \
add_collateral \
--collateral_token_addr "C...." \
--collateral_token_amount "4000000000" \
--collateral_token_symbol "ABC" \
--collateral_addr "G..."

Let's analyze the parameters one by one:

  • collateral_token_addr: It is the contract address of the token that is going to be used as collateral.
  • collateral_token_amount: It is the amount of collateral deposited in units of the collateral_token_addr.
  • collateral_token_symbol: The token code.
  • collateral_addr: The address that holds the collateral, from which the funds will be sent to the contract.

Adding collateral is allowed only if all of the following conditions are met:

  • company role only
  • contract not paused
  • the source address has enough collateral token balance
  • only one collateral token type is used for the instance

Calculating the collateral level

Collateral valuation depends on the price_oracle configured at deployment time (see Deploying the contract).

The contract calculates a collateral level by:

  1. If there are no remaining payment obligations, the collateral level is 10_000 (100%) by definition — no further calculation is needed.
  2. Fetch the collateral token's price and the contract token's price from the oracle, both expressed in the oracle's base asset.
  3. Compute the cross price (collateral / contract token) from those two prices, both at the oracle's decimal scale.
  4. Convert the collateral amount into the contract token denomination by multiplying it by the cross price.
  5. Divide the resulting collateral value by the remaining payment obligations, scaling by 10_000 so the result is expressed in basis points (e.g. 6_000 = 60% coverage, 10_000 = 100% coverage).

If the valuation cannot be established or is insufficient, the operation fails.

For the oracle to be compatible with Equillar, it must implement the following trait:

#[contracttype]
#[derive(Clone)]
pub enum Asset {
Stellar(Address),
Other(Symbol),
}

#[contracttype]
#[derive(Clone)]
pub struct PriceData {
pub price: i128,
pub timestamp: u64,
}

#[contractclient(name = "ReflectorClient")]
pub trait ReflectorOracle {
fn base(env: &Env) -> Asset;
fn decimals(env: &Env) -> u32;
fn lastprice(env: &Env, asset: Asset) -> Option<PriceData>;
}

Reflector is a compatible oracle and has been used as the reference implementation during development. However, any oracle contract that implements this trait can be used by the integrator.

Worked example

Assumptions:

  • Contract token: USDC (7 decimals). Remaining payment obligations: 10000_0000000 (10,000 USDC).
  • Collateral token: backed 1:1 by BTC (7 decimals). Held amount: 1_500_000 token units (0.15 BTC).
  • Oracle prices (in the oracle's base asset, USD): BTC = 60,000, USDC = 1.

Step 1 — Prices from the oracle

  • Collateral token price: 60,000 USD
  • Contract token price: 1 USD

Step 2 — Cross price

cross_price = collateral_price / contract_token_price = 60,000 / 1 = 60,000

→ 1 unit of collateral token is worth 60,000 units of the contract token.

Step 3 — Collateral value in contract token terms

collateral_value = collateral_amount * cross_price = 0.15 * 60,000 = 9,000 USDC

Step 4 — Collateral level

collateral_level = (collateral_value / payment_obligations) * 10_000 = (9,000 / 10,000) * 10_000 = 9,000 (basis points)

This corresponds to a 90% coverage level — the collateral almost, but not fully, covers the outstanding payment obligations.

Payments with collateral

Liquidating positions using the collateral can be achieved by the function pay_with_collateral(token_id).

stellar contract invoke \
--id <YOUR_CONTRACT_ID> \
-s admin \
--network testnet \
-- \
pay_with_collateral \
--token_id 1234 \

This function transfers the amount of collateral corresponding to the position's address (operator role) based on the position's outstanding obligations relative to the total outstanding obligations. Let's analyze a working example in the next section.

Liquidating positions with collateral is allowed only if all of the following conditions are met:

  • admin role only
  • Position is not completed
  • There is collateral in the contract
  • Contract is not paused

Worked example. Distributing collateral

We'll start with the data from the previous section:

  • Contract token: USDC (7 decimals). Remaining payment obligations: 10000_0000000 (10,000 USDC).
  • Collateral token: backed 1:1 by BTC (7 decimals). Held amount: 1_500_000 token units (0.15 BTC).
  • Oracle prices (in the oracle's base asset, USD): BTC = 60,000, USDC = 1.

We also assume the following additional data:

  • The 10,000 USDC in total obligations is spread across three positions. Only the first two are liquidated in this walkthrough:
    • Position 1: 3,000 USDC in outstanding obligations
    • Position 2: 4,000 USDC in outstanding obligations
    • Position 3: 3,000 USDC in outstanding obligations

Checking whether the collateral needs to be capped

Before distributing, the contract checks whether the collateral it holds is worth more than what's currently owed:

collateral_value = collateral_held * cross_price = 0.15 * 60,000 = 9,000 USDC
remaining_obligations = 10,000 USDC

Since 9,000 < 10,000, the collateral is not capped — there isn't more collateral than debt, so the full 0.15 BTC held remains available for proportional distribution.

Liquidating position 1

  • Position 1's share of the total obligations: 3,000 / 10,000 = 30%
  • Collateral transferred: 0.15 * 0.30 = 0.045 BTC
  • We transfer 0.045 collateral tokens to position 1's address

Liquidating position 2

  • Remaining collateral after position 1: 0.15 - 0.045 = 0.105 BTC
  • Remaining obligations after position 1: 10,000 - 3,000 = 7,000 USDC
  • The contract re-checks the cap: collateral_value = 0.105 * 60,000 = 6,300, still less than 7,000 — no cap applied
  • Position 2's share of the remaining obligations: 4,000 / 7,000 ≈ 57.14%
  • Collateral transferred: 0.105 * 0.5714 = 0.06 BTC
  • We transfer 0.06 collateral tokens to position 2's address

After these two payments, 0.15 - 0.045 - 0.06 = 0.045 BTC remain in the contract, and 3,000 USDC in obligations are still outstanding (position 3), which would be settled the same way in a subsequent call.

Worked example. Collateral in excess of obligations

Now let's look at the opposite scenario: the collateral held is worth more than what's owed, so the cap described above becomes relevant.

Assumptions:

  • Collateral held: 1 BTC (7 decimals).
  • Oracle price: BTC = 60,000 USDC.
  • Three positions to settle, in this order:
    • Position A: 5,000 USDC in outstanding obligations
    • Position B: 6,000 USDC in outstanding obligations
    • Position C: 7,000 USDC in outstanding obligations
    • Total obligations: 5,000 + 6,000 + 7,000 = 18,000 USDC

Liquidating position A

  • Collateral check: collateral_value = 1 * 60,000 = 60,000, which is greater than the 18,000 owed — the cap applies.
  • Capped collateral amount: required_collateral = remaining_obligations / cross_price = 18,000 / 60,000 = 0.3 BTC
  • Position A's share of the total obligations: 5,000 / 18,000 ≈ 27.78%
  • Collateral transferred: 0.3 * 0.2778 ≈ 0.0833 BTC — worth exactly 5,000 USDC, matching A's debt precisely.

Liquidating position B

  • Remaining collateral: 1 - 0.0833 = 0.9167 BTC. Remaining obligations: 18,000 - 5,000 = 13,000 USDC.
  • Collateral check: collateral_value = 0.9167 * 60,000 ≈ 55,000, still greater than 13,000 — the cap applies again.
  • Capped collateral amount: required_collateral = 13,000 / 60,000 ≈ 0.2167 BTC
  • Position B's share of the remaining obligations: 6,000 / 13,000 ≈ 46.15%
  • Collateral transferred: 0.2167 * 0.4615 ≈ 0.1 BTC — worth exactly 6,000 USDC, matching B's debt precisely.

Liquidating position C

  • Remaining collateral: 0.9167 - 0.1 = 0.8167 BTC. Remaining obligations: 13,000 - 6,000 = 7,000 USDC.
  • Collateral check: collateral_value = 0.8167 * 60,000 ≈ 49,000, still greater than 7,000 — the cap applies once more.
  • Capped collateral amount: required_collateral = 7,000 / 60,000 ≈ 0.1167 BTC
  • Position C's share of the remaining obligations: 7,000 / 7,000 = 100%
  • Collateral transferred: 0.1167 * 1 = 0.1167 BTC — worth exactly 7,000 USDC, matching C's debt precisely.

Outcome

Every position receives collateral worth exactly what it was owed — no more, no less — even though the contract held far more collateral than necessary. In total, 0.0833 + 0.1 + 0.1167 = 0.3 BTC were liquidated (worth 18,000 USDC, matching total obligations exactly), leaving 1 - 0.3 = 0.7 BTC of excess collateral still held by the contract. That excess can later be recovered by the company through return_collateral_to_company.

Returning collateral to the company

If the company added collateral to the contract but it was never needed, and all payment obligations have been successfully settled, the full collateral amount can be returned to the company address by calling the return_collateral_to_company function.

stellar contract invoke \
--id <YOUR_CONTRACT_ID> \
-s admin \
--network testnet \
-- \
return_collateral_to_company

Calling this function transfers the entire collateral balance back to the company.

Events and balance after a collateral deposit

When collateral is added to the contract through add_collateral, the balance is updated via recalculate_from_collateral_received:

  • collateral_received is increased by the deposited amount

Note: the balance update and event emission only happen if the collateral level can be successfully calculated — for example, if the oracle call fails, neither the balance nor the event are updated/emitted.

This update is published through the standard ContractBalanceUpdated event (see Fundraising and positions section).

In addition, the contract emits a dedicated event with the deposit details:

CollateralDeposited:

CollateralDeposited {
addr, // the address of the contract
current_collateral_amount, // the total collateral held by the contract, including this deposit
total_deposited, // the amount transferred in this specific call
token_address, // the address of the collateral token
token_symbol, // the symbol of the collateral token
}

Reading this event with stellar-cli

stellar events \
--id <YOUR_CONTRACT_ID> \
--network testnet \
--start-ledger <LEDGER_NUMBER> \
--type contract \
--topic "CollateralDeposited,*"

Events and balance after collateral liquidation

When collateral is liquidated to cover payment obligations through pay_with_collateral, the balance is updated via recalculate_from_collateral_liquidated:

  • collateral_liquidated is increased by the liquidated amount
  • payment_obligations is reduced by the remaining obligations covered by this liquidation

This update is published through the standard ContractBalanceUpdated event (see Fundraising and positions section).

In addition, the contract emits a dedicated event with the payment details:

CollateralSent:

CollateralSent {
addr, // the address of the contract
to, // the address receiving the liquidated collateral
total_sent, // the total amount sent
}

Reading this event with stellar-cli

stellar events \
--id <YOUR_CONTRACT_ID> \
--network testnet \
--start-ledger <LEDGER_NUMBER> \
--type contract \
--topic "CollateralSent,*"

Events and balance after returning collateral to the company

When the full collateral amount is returned to the company through return_collateral_to_company, the balance is updated via recalculate_from_collateral_returned:

  • collateral_returned is increased by the returned amount

This update is published through the standard ContractBalanceUpdated event (see Fundraising and positions section above).

In addition, the contract emits a dedicated event with the return details:

CollateralReturned:

CollateralReturned {
addr, // the address of the contract
to, // the company address receiving the collateral
total_returned, // the total amount returned
}

Reading this event with stellar-cli

stellar events \
--id <YOUR_CONTRACT_ID> \
--network testnet \
--start-ledger <LEDGER_NUMBER> \
--type contract \
--topic "CollateralReturned,*"