Skip to main content

Core Contracts

Lorem ipsum dolor sit amet, consectetur adipiscing elit — description of the main protocol contracts and their responsibilities.

Clearinghouse

Overview

The Clearinghouse is the debt management layer that enables leveraged positions through virtual token (fxToken) minting and burning. It tracks per-account debt obligations and controls the supply of all fxTokens in the system. No fxToken can be minted or burned without going through the Clearinghouse.

Why Virtual Tokens?

Ollo does not require real FX assets. Instead, it mints synthetic fxTokens (e.g., fxUSD, fxJPY) on demand to represent position exposure. These tokens flow through the order book as if they were real assets, but their lifecycle is fully managed by the Clearinghouse. This means:

  • Positions can be opened without sourcing the underlying
  • The order book operates on real ERC20 token transfers
  • All debt is tracked and cleared when positions close
  • PnL is settled in USDC margin, not in the virtual tokens themselves

Architecture

┌─────────────────┐     registerFxToken()     ┌──────────────────────┐
│ Owner │ ─────────────────────────► │ Clearinghouse │
└─────────────────┘ setFxEngine() │ │
│ • isTokenRegistered │
┌─────────────────┐ authorizeClearinghouse() │ • debt[acct][token] │
│ FxEngine │ ──────────────────────────►│ • authorizedCH[] │
└─────────────────┘ │ • registeredTokens │
└──────────┬───────────┘

mint() / burn() / │
burnSurplus() / forgiveDebt() │

┌──────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ FxAccount (authorized) │
│ │
│ Opening position: clearinghouse.mint(token, self, amt) │
│ Closing position: clearinghouse.burn(token, self, amt) │
│ Profit (surplus): clearinghouse.burnSurplus(token, ...) │
│ Loss (forgive): clearinghouse.forgiveDebt(token, amt) │
│ Cancel order: _burnUnusedTokens(token) │
└─────────────────────────────────────────────────────────────┘


┌─────────────────┐
│ FxToken │
│ (ERC20) │
│ • mint() │
│ • burn() │
└─────────────────┘

How It Works

Authorization Model

The Clearinghouse uses a two-layer authorization system:

  1. Owner — Can register new fxTokens and set the FxEngine address
  2. FxEngine — The only address that can authorize or deauthorize clearinghouse participants
  3. Authorized Clearinghouses — FxAccount contracts that are authorized when accounts are created via FxEngine.createAccount()

When FxEngine creates an account, it calls clearinghouse.authorizeClearinghouse(accountAddress, true), granting that FxAccount the right to mint, burn, and manage debt.

Debt Tracking

Every mint() call records a debt obligation:

debt[msg.sender][token] += amount

Where msg.sender is the FxAccount contract. This means each account's debt is tracked per-token. A single account may hold debt in multiple fxTokens simultaneously (e.g., fxJPY from a LONG and fxUSD from a SHORT in different markets).

Token Lifecycle

1. Minting (Opening a Position)

When an FxAccount opens a position, it calls clearinghouse.mint() to create the tokens needed for the order book:

// LONG USD/JPY: mint quote tokens (fxJPY) to place as BID
clearinghouse.mint(config.quoteFxToken, address(this), spend);

// SHORT USD/JPY: mint base tokens (fxUSD) to place as ASK
clearinghouse.mint(config.baseFxToken, address(this), positionSize);

Each mint increases the account's debt for that token.

2. Burning Against Debt (Closing — Neutral Case)

When tokens are returned after a fill and the account holds both tokens and debt, burn() reduces both:

// Burns tokens AND reduces debt by `amountToBurn`
clearinghouse.burn(token, address(this), amountToBurn);

The burn requires debt[msg.sender][token] >= amount and that the account holds sufficient token balance.

3. Burning Surplus (Closing — Profit Case)

When an account closes at a profit, it holds more tokens than its debt. After burning against debt, the surplus is burned without debt reduction:

// Burns tokens with NO debt reduction (surplus from profit)
clearinghouse.burnSurplus(token, address(this), balance);

This is safe because the profit is already realized in the account's USDC margin via _realizePnL(). The surplus tokens are phantom — they represent the counterparty's loss, not real value.

4. Forgiving Debt (Closing — Loss Case)

When an account closes at a loss, it may have debt remaining after all tokens are burned. This debt is forgiven:

// Reduces debt with NO token burn (loss already deducted from margin)
clearinghouse.forgiveDebt(token, currentDebt);

This is safe because the loss is already deducted from the account's USDC margin. The remaining debt is phantom — it represents exposure that no longer exists.

5. Cancellation Cleanup

When an order is cancelled, any minted-but-unused tokens are burned against their debt:

function _burnUnusedTokens(address token) internal {
uint256 balance = ERC20(token).balanceOf(address(this));
if (balance > 0) {
uint256 currentDebt = clearinghouse.getDebt(address(this), token);
if (currentDebt > 0) {
uint256 amountToBurn = balance > currentDebt ? currentDebt : balance;
clearinghouse.burn(token, address(this), amountToBurn);
}
}
}

Contract Interface

Admin Functions

/// @notice Register a new fx token for minting/burning
/// @dev Only callable by owner. Reverts if already registered.
/// @param token Address of the FxToken contract
function registerFxToken(address token) external onlyOwner;

/// @notice Grant or revoke clearinghouse privileges
/// @dev Only callable by FxEngine. Called when accounts are created.
/// @param clearinghouse Address to authorize (typically an FxAccount)
/// @param authorized Whether to authorize or deauthorize
function authorizeClearinghouse(address clearinghouse, bool authorized) external onlyFxEngine;

/// @notice Set the FxEngine address
/// @dev Only callable by owner
/// @param _fxEngine Address of the FxEngine contract
function setFxEngine(address _fxEngine) external onlyOwner;

Minting

/// @notice Mint new fx tokens and record debt obligation
/// @dev Only callable by authorized clearinghouses (FxAccounts)
/// @dev Reverts if token not registered or amount is zero
/// @param token Address of the fx token to mint
/// @param to Address to receive the minted tokens
/// @param amount Amount of tokens to mint
function mint(address token, address to, uint256 amount) external onlyAuthorized;

Burning

/// @notice Burn fx tokens and reduce debt
/// @dev Requires sufficient debt and sufficient token balance
/// @param token Address of the fx token to burn
/// @param from Address to burn tokens from
/// @param amount Amount of tokens to burn
function burn(address token, address from, uint256 amount) external onlyAuthorized;

/// @notice Burn surplus tokens without requiring debt
/// @dev Used when PnL is already realized in margin
/// @param token Address of the fx token to burn
/// @param from Address to burn tokens from
/// @param amount Amount of tokens to burn
function burnSurplus(address token, address from, uint256 amount) external onlyAuthorized;

/// @notice Forgive debt without burning tokens
/// @dev Used when loss is settled via margin deduction
/// @param token Address of the fx token
/// @param amount Amount of debt to forgive
function forgiveDebt(address token, uint256 amount) external onlyAuthorized;

View Functions

/// @notice Get debt amount for an account and token
/// @param account Address of the FxAccount
/// @param token Address of the fx token
/// @return The debt amount
function getDebt(address account, address token) external view returns (uint256);

FxToken

FxToken is a minimal ERC20 with mint/burn restricted to the Clearinghouse:

contract FxToken is ERC20 {
address public immutable clearinghouse;

modifier onlyClearinghouse() {
if (msg.sender != clearinghouse) revert OnlyClearinghouse();
_;
}

function mint(address account_, uint256 amount_) external onlyClearinghouse;
function burn(address account_, uint256 amount_) external onlyClearinghouse;
}

Each FxToken is constructed with a name (e.g., "FX USD"), symbol (e.g., "fxUSD"), and the Clearinghouse address. Token precision is always 18 decimals.

Token Clearing on Position Close

When a position is fully closed, _clearSingleTokenAndDebt() in FxAccount handles all cases:

function _clearSingleTokenAndDebt(address token) internal returns (uint256 cleared) {
uint256 balance = ERC20(token).balanceOf(address(this));
uint256 currentDebt = clearinghouse.getDebt(address(this), token);

// Step 1: Burn tokens against debt (min of both)
if (balance > 0 && currentDebt > 0) {
uint256 amountToBurn = balance < currentDebt ? balance : currentDebt;
clearinghouse.burn(token, address(this), amountToBurn);
}

// Step 2: Handle surplus tokens (profit case)
if (balance > 0) {
clearinghouse.burnSurplus(token, address(this), balance);
}

// Step 3: Handle remaining debt (loss case)
if (currentDebt > 0) {
clearinghouse.forgiveDebt(token, currentDebt);
}
}

This three-step process guarantees that after a position close, the FxAccount holds zero balance and zero debt for the relevant tokens.

Events

event FxTokenRegistered(address indexed token);
event ClearinghouseAuthorized(address indexed clearinghouse, bool authorized);
event TokensMinted(address indexed account, address indexed token, uint256 amount);
event TokensBurned(address indexed account, address indexed token, uint256 amount);
event SurplusBurned(address indexed account, address indexed token, uint256 amount);
event DebtForgiven(address indexed account, address indexed token, uint256 amount);

Errors

error Unauthorized();          // Caller not authorized
error TokenNotRegistered(); // Token not registered with Clearinghouse
error TokenAlreadyRegistered();// Token already registered
error InsufficientDebt(); // Not enough debt to burn/forgive
error InsufficientBalance(); // Not enough token balance to burn
error ZeroAmount(); // Amount must be non-zero

Access Control

FunctionAccess
registerFxTokenOwner only
setFxEngineOwner only
authorizeClearinghouseFxEngine only
mintAuthorized clearinghouses (FxAccounts)
burnAuthorized clearinghouses (FxAccounts)
burnSurplusAuthorized clearinghouses (FxAccounts)
forgiveDebtAuthorized clearinghouses (FxAccounts)
getDebtPermissionless (view)

Security Considerations

No Unbacked Minting

Every mint() records debt. The system ensures that debt is always resolved — either by burning tokens back, burning surplus, or forgiving debt after margin-settled losses. At steady state, total minted = total burned + total surplus burned, and total debt created = total debt reduced + total debt forgiven.

Clearinghouse Authorization

Only FxEngine can grant clearinghouse authorization, and it does so exclusively during createAccount(). This prevents arbitrary addresses from minting tokens or manipulating debt.

Immutable Clearinghouse on FxToken

Each FxToken's clearinghouse address is set at construction and is immutable. No admin can redirect minting authority after deployment.