> For the complete documentation index, see [llms.txt](https://docs.symm.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.symm.io/security-and-architecture/contract-architecture-overview.md).

# Contract Architecture Overview

Symmio is an intent-based bilateral derivatives protocol. Retail users (PartyA) trade against professional market makers (PartyB), every critical operation is gated by cryptographically verified off-chain signatures, and the whole system is built on the **Diamond Standard (EIP-2535)**, so individual facets can be added, replaced, or removed without changing the diamond's address or disturbing storage.

What was once a single diamond is now a small network of diamonds with clearly separated responsibilities, plus an externalized signature verifier and a privileged operator (the [ClearingHouse](/contract-documentation/symmio-perps-v0.8.5/diamond-core-facets/clearing-house-facet.md)) for the cases the decentralized flow can't handle. This page is a tour of what lives where, how the account hierarchy is structured, and how a quote moves through the system end to end.

### The Diamond contracts

Three diamonds make up the live protocol surface, plus the supporting contracts they coordinate with.

* **Symmio Core (Perps Diamond).** The main diamond for facilitating trades between the trader (PartyA) and the solver (PartyB). It handles sending quotes, locking, opening, closing, settling, and liquidating.
* **Account Layer.** A standalone diamond proxy that replaces the per-frontend MultiAccount contracts. It owns every user's `SubAccounts` and deterministic `VirtualAccounts`, registers affiliates, distributes affiliate fees to stakeholders, supports express deposits, and exposes hooks for custom on-chain logic (NFT mints, loyalty points, cashback).
* **Instant Layer.** A diamond that mediates user-signed operations using EIP-712 `SignedOperation` structs, flex fields, templates, and a delegation system. Operators with `OPERATOR_ROLE` submit batches; the Instant Layer routes them through the Account Layer's `_call` so Symmio Core sees the right trading account.

Around them:

* **`MuonSignatureVerifier`.** External contract, addressed from `GlobalAppStorage.signatureVerifier`. Manages multiple TSS public keys and gateway signers with per-`MuonFunction` authorization. See the Muon Architecture page.
* **ClearingHouse.** Privileged operator (`CLEARING_HOUSE_ROLE`) that handles cross-mode PartyB insolvency and stuck PartyA liquidations. It's trusted to provide its own UPNL and prices; the contract enforces lifecycle invariants.
* **Express Provider / Virtual Provider contracts.** Per-chain liquidity contracts that participate in the new Withdraw System.
* **PledgeFacet vault.** Holds PartyB pledge collateral (slashable), with a two-step request-approval withdrawal flow.
* **Liquidation Insurance Vault.** Receives the excess of liquidator profit above `maxLiquidationProfitPerPosition`.

### Storage

* `DiamondStorage`: contract ownership.
* `GlobalAppStorage`: collateral, fee collector, role mappings, pause states, emergency mode, balance limits, and the like.
* `AccountStorage`: user balances, allocated balances, locked values (CVA, LF, MM), pending locks, nonces, suspension status, liquidation details.
* `QuoteStorage`: quote structs, pending quote arrays, position counts, the quote ID counter.
* `MAStorage`: cooldown durations, liquidator share, PartyB registration status, liquidation timestamps, withdraw cooldown period.
* `SymbolStorage`: trading symbol configurations including leverage limits, fees, funding rate parameters, and the new **`symbolType`** mapping.
* `MuonStorage`: kept for legacy fields, though the active key set lives in `MuonSignatureVerifier`.

### Account hierarchy: SubAccounts and Virtual Accounts

```
User (EOA)
 └─ SubAccount   (bound to affiliate + Symmio core + isolation type)
     └─ Virtual Account 1   (acts as partyA on Symmio Core)
     └─ Virtual Account 2
     └─ …
```

`SubAccounts` are the user's top-level organizational unit: each one is bound to a specific frontend (affiliate), a Symmio core instance, and an isolation type chosen at creation. A user can keep one for conservative trades and another for an aggressive book.

`Virtual Accounts (VAs)` sit beneath SubAccounts and provide position isolation. Each VA is an independent trading address on Symmio Core with its own balance, allocated margin, and positions. A liquidation in one VA can't touch another. When a VA's last position closes, its funds are swept back to the parent SubAccount automatically and the address is recycled.

Funds flow:

```
User ─deposit──►  SubAccount ─addMargin─►  Virtual Account
                  SubAccount ◄─sweep────  Virtual Account (on last position close)
```

`addMarginToNextVA` lets a frontend (or a delegated service) pre-fund the *next* VA address before it exists, which is how trigger-market orders pre-fund the VA that the subsequent `sendQuoteWithAffiliateAndData` will create.

#### Isolation types

Chosen at SubAccount creation, with a "Single VA mode" toggle for the market-bucketed types:

* `POSITION`: one VA per quote. Maximum isolation; each position has its own margin pool.
* `MARKET`: one VA per market. All BTC trades share a VA, all ETH trades share another.
* `MARKET_DIRECTION`: one VA per (market, direction). BTC longs share a VA; BTC shorts share a different one.
* `CUSTOM`: caller-supplied VA addresses, useful for advanced strategies.

### Roles and admin model

Role-based access control is still anchored on `DEFAULT_ADMIN_ROLE`, but v0.8.5 adds a delegation layer so an admin team doesn't have to bottleneck every grant and revoke through one address.

```solidity
// GlobalAppStorage
mapping(bytes32 => mapping(address => bool)) roleAdmins;
```

A `roleAdmin` for a specific role can grant and revoke that role without touching any other. Combined with the existing `hasRole` mapping, the system has a clear hierarchy: **Owner → Default Admin → Role Admin → Role Holder**.

The active roles:

| Role                            | Permissions                                                                                                                              |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `DEFAULT_ADMIN_ROLE`            | Grant/revoke all roles, set collateral, emergency mode, unsuspend addresses, set fee collector and balance limits, designate role admins |
| `PAUSER_ROLE`                   | Pause global operations, liquidation, accounting, PartyA/PartyB actions                                                                  |
| `UNPAUSER_ROLE`                 | Unpause any paused function                                                                                                              |
| `PARTY_B_MANAGER_ROLE`          | Register and deregister PartyB addresses; toggle ADL (Auto-Deleveraging) eligibility                                                     |
| `SYMBOL_MANAGER_ROLE`           | Add symbols, configure leverage, trading fees, funding parameters, **symbol types**                                                      |
| `SETTER_ROLE`                   | Configure cooldowns, liquidator share, force close parameters, withdraw cooldown                                                         |
| `MUON_SETTER_ROLE`              | Set the `MuonSignatureVerifier` address; legacy Muon config                                                                              |
| `SUSPENDER_ROLE`                | Suspend addresses and bridge transactions                                                                                                |
| `LIQUIDATOR_ROLE`               | Decentralized liquidator path (still active for isolated PartyB and standard PartyA)                                                     |
| `CLEARING_HOUSE_ROLE`           | ClearingHouse operator for cross-mode PartyB insolvency and stuck PartyA liquidations                                                    |
| `OPERATOR_ROLE` (Instant Layer) | Submit `executeBatch` / `executeTemplate`                                                                                                |

Diamond ownership transfer is **two-step**: the current owner sets a pending owner, and the pending owner must call `acceptOwnership()` to complete the transfer. Mistakes are recoverable until the second transaction lands.

### Quote lifecycle

#### Stage 1: Quote creation (PENDING)

PartyA initiates via one of:

* `sendQuote(partyBsWhiteList, symbolId, positionType, orderType, price, quantity, cva, lf, partyAmm, partyBmm, maxFundingRate, deadline, affiliate, upnlAndPriceSig)`: the original signature.
* `sendQuoteWithAffiliate(...)`: kept for compatibility.
* `sendQuoteWithAffiliateAndData(..., bytes data)`: same as `sendQuoteWithAffiliate()`, but with arbitrary `bytes` attached to the quote.

Margin components:

* **CVA** (Credit Valuation Adjustment): penalty paid by the liquidated party to the counterparty.
* **MM** (Maintenance Margin): `partyAmm` and `partyBmm`, considered in liquidation calculations.
* **LF** (Liquidation Fee): reward for the liquidator.

Funds lock in `pendingLockedBalances[partyA]`. The `SingleUpnlAndPriceSig` Muon signature is required here, unless PartyA is bound to a single PartyB.

#### Stage 2: Quote locking (LOCKED)

PartyB calls `lockQuote(quoteId, upnlSig, increaseNonce)`. `LibMuon.verifyPartyBUpnl` checks PartyB's UPNL, `LibPartyB.checkPartyBValidationToLockQuote` enforces validation rules, and `partyBPendingLockedBalances` updates. PartyB can release via `unlockQuote()`, reverting the quote to `PENDING` or `EXPIRED`.

#### Stage 3: Position opening (OPENED)

`openPosition(quoteId, filledAmount, openedPrice, upnlSig)` supports partial fills: filling 20 of 100 units creates a new quote for the remainder. Trading fees are applied, solvency is checked via `LibSolvency.isSolventAfterOpenPosition`, and the call requires a `PairUpnlAndPriceSig` attestation.

In the current version, **`PartyBGroupActionsFacet`** adds batch variants:

```solidity
function openPositions(
  uint256[] memory quoteIds,
  uint256[] memory filledAmounts,
  uint256[] memory openedPrices,
  PairUpnlAndPricesSig memory batchSig
) external;
```

One Muon signature, many positions. The same idea applies to `fillCloseRequests`. This keeps solver response time low under heavy load.

#### Stage 4: Position closing

PartyA requests closure via `requestToClosePosition(quoteId, closePrice, quantityToClose, orderType, deadline)`, moving the quote to `CLOSE_PENDING`. PartyB executes via `fillCloseRequest(quoteId, filledAmount, closedPrice, upnlSig)`. **LIMIT** orders allow multiple partial fills; **MARKET** orders require full execution. Both check solvency via `LibSolvency.isSolventAfterClosePosition`.

* **`fillCloseRequestToLiquidation`:** when a normal close would push PartyA into insolvency, this calculates the maximum partial close that brings them exactly to the liquidation threshold.
* **ADL Close:** PartyBs (enabled with `PARTY_B_MANAGER_ROLE`) can unilaterally close to reduce risk exposure, with no solvency check on themselves. Backed by **pledge collateral** that can be slashed if ADL is abused.

#### Cancellation

The cancellation transitions are unchanged: `requestToCancelQuote` (`PENDING` → `CANCELED`, or `LOCKED` → `CANCEL_PENDING`), `acceptCancelRequest` (PartyB accepts), `requestToCancelCloseRequest` (`CLOSE_PENDING` → `CANCEL_CLOSE_PENDING`), `acceptCancelCloseRequest` (returns to `OPENED`).

### Liquidation

#### PartyA liquidation (4-step)

Trigger condition: total UPNL `< -(allocated_balance - mm - pending_locked_values)`.

1. `liquidatePartyA(partyA, upnlSig)`: records the liquidation timestamp and `LiquidationDetail`.
2. `setSymbolsPrice(partyA, priceSig)`: establishes liquidation prices for all position symbols.
3. `liquidatePendingPositionsPartyA(partyA)`: clears unlocked quotes.
4. `liquidatePositionsPartyA(partyA, quoteIds[])`: liquidates the specified open positions.

The `LiquidationDetail` captures `liquidationId`, `liquidationType` (NORMAL / LATE / OVERDUE), `upnl`, `totalUnrealizedLoss`, `deficit`, `liquidationFee`, `timestamp`, `involvedPartyBCounts`, `partyAAccumulatedUpnl`, and a `disputed` flag.

Per-position liquidator profit is capped at `maxLiquidationProfitPerPosition`; the excess flows to a configured Liquidation Insurance Vault that the protocol can reuse to reimburse PartyBs hit by late-liquidation losses.

In `LATE` and `OVERDUE` PartyA liquidations, pending fee reimbursements are routed to a ClearingHouse-controlled escrow rather than back to PartyA. Deferred excess balance (funds added after the historical insolvency point) still goes back to PartyA.

When the on-chain state is too inconsistent for the dispute resolver to fix, the ClearingHouse can take over the liquidation entirely, reset the state, and finish it with its own price feed.

#### PartyB liquidation

Isolated-mode PartyB uses a 2-step flow:

1. `liquidatePartyB(partyB, partyA, upnlSig)`.
2. `liquidatePositionsPartyB(partyB, partyA, priceSig)`.

Cross-mode PartyB doesn't use that flow. Because cross mode pools all collateral into a single `address(0)` bucket shared across PartyAs, a single insolvency event affects everyone with positions against that PartyB. The **ClearingHouse** runs the unwind:

```
liquidateCrossPartyB(partyB, liquidationId, upnl, timestamp)
   ▶ deallocateForClearingHouse(partyB, parties, allocationKeys, amounts)
   ▶ liquidatePendingPositionsForClearingHouse(partyB, partyAs)
   ▶ liquidatePositionsForClearingHouse(partyB, quoteIds, prices)
   ▶ distributeForClearingHouse(partyB, receivers, …)
   ▶ settleCrossPartyBLiquidation(partyB)
```

Settlement requires all positions closed and the deallocated pool fully distributed. The ClearingHouse provides UPNL and prices directly; the contract trusts the operator and enforces lifecycle invariants.

#### Soft liquidation

For PartyBs, the protocol supports a tiered warning system before hard liquidation. As balance approaches the danger zone, the [ClearingHouse](/contract-documentation/symmio-perps-v0.8.5/diamond-core-facets/clearing-house-facet.md) can issue on-chain notices and small penalties at configurable thresholds, giving the solver time to top up funds instead of disrupting every user on every frontend with an immediate close-all.

#### Cross-mode settlement during PartyA liquidation

In cross mode, a PartyB's solvency includes unrealized profit from all its other counterparties, so its raw allocated balance can be much lower than the value it actually controls. `settlePartyBUpnlForLiquidation` (on `SettlementFacet`) lets a liquidator realize a cross-mode PartyB's UPNL from positions with other (non-liquidated) PartyAs before the PartyA liquidation settlement draws funds. Cross-mode PartyBs with insufficient raw balance revert until this step runs; isolated PartyBs keep the prior capped-payment behavior.

### Funding rate mechanics

In the previous version, funding was charged per quote, so PartyB had to process every individual position at each epoch, `O(quotes)`. The current version uses an **accumulated weighted average** at the symbol level.

```solidity
struct FundingFee {
    int256 cumulativeRate;   // running weighted average, pre-multiplied by price
    uint256 lastUpdate;
    uint256 epochDuration;
    // … plus dynamic-epoch scaling fields
}
```

When a solver sets a new rate, it blends into the running average. Rates are pre-multiplied by market price at storage time, so the calculation doesn't need a price lookup. Dynamic epoch-duration changes are supported with automatic rate scaling.

**Aggregated positions** track running totals of size and notional per `(party, symbol, positionType)`, so UPNL calculations iterate over active *symbols* instead of quotes. For a solver with 5,000 positions across 50 symbols, solvency goes from `O(5000)` to `O(50)`. Aggregate funding debt is tracked the same way.

`FundingRateFacet.chargeFundingRate(partyA, quoteIds[], rates[], upnlSig)` and `chargeAccumulatedFundingFee(...)` apply periodic adjustments, with timing controlled by symbol-specific `fundingRateEpochDuration` and `fundingRateWindowTime`.

### Withdraw System

Withdrawals draw only from an account's **free (deallocated) balance**, so the flow has a mandatory first step:

```
allocated margin (in a VA)
  → deallocate  (removeMargin: deallocate + internalTransferToBalance → SubAccount balance)
  → free balance
  → initiateWithdraw  → cooldown → finalizeWithdrawRequest
```

`initiateWithdraw` requires `balances[user] >= amount` and reverts with `"WithdrawFacet : Insufficient balance"` otherwise — funds still allocated to a position can't be withdrawn. Use `removeMargin(virtualAccount, amount, upnlSig)` to deallocate and sweep margin back to the parent SubAccount before initiating.

A withdrawal request is composed of one or more receiver parts, each routed through one of four withdrawal paths.

Cooldown is anchored to the user's last deallocation, not to the withdrawal initiation — because deallocation is what produces the withdrawable balance in the first place:

```
cooldownEndTime = max(deallocateTimestamp + withdrawCooldownPeriod, block.timestamp)
```

Later deallocations don't reset the cooldown of an already-initiated withdrawal. `getWithdrawableTime(user)` returns the earliest time a withdrawal initiated now could finalize. Express providers front the funds for a fee. Virtual providers deliver cross-chain; they implement lifecycle callbacks (`onWithdrawRequest`, `acceptWithdrawRequest`, `onWithdrawComplete`) that the protocol calls during the lifecycle. Cancellation has its own state machine, with a blackout window for in-flight provider acceptances and an admin force-cancel.

| Type             | Express | Virtual | Description                                |
| ---------------- | ------- | ------- | ------------------------------------------ |
| Normal (Classic) | ✗       | ✗       | Standard 12h cooldown, same-chain delivery |
| Pure Express     | ✓       | ✗       | Instant same-chain withdrawal              |
| Pure Virtual     | ✗       | ✓       | Standard cooldown, cross-chain delivery    |
| Virtual Express  | ✓       | ✓       | Instant cross-chain withdrawal             |

**Cooldown is anchored to the user's last deallocation**, not to the withdrawal initiation:

```
cooldownEndTime = max(deallocateTimestamp + withdrawCooldownPeriod, block.timestamp)
```

Later deallocations don't reset the cooldown of an already-initiated withdrawal. `getWithdrawableTime(user)` returns the earliest time a withdrawal initiated *now* could finalize. **Express providers** front the funds for a fee. **Virtual providers** deliver cross-chain; they implement lifecycle callbacks (`onWithdrawRequest`, `acceptWithdrawRequest`, `onWithdrawComplete`) that the protocol calls during the lifecycle. **Cancellation** has its own state machine, with a blackout window for in-flight provider acceptances and an admin force-cancel.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

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

```
GET https://docs.symm.io/security-and-architecture/contract-architecture-overview.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
