Account Layer Diamond

AccountLayer

The Symmio AccountLayer is the unified account and affiliate management layer for the Symmio protocol. It replaces the old per-frontend MultiAccount contracts with a single Diamond proxy that manages every user's accounts, standardizes affiliate onboarding and fee handling, and provides flexible position isolation without deploying a contract per user.

The Problem It Solves

Previously, each frontend deployed its own MultiAccount contract, which Symmio registered individually. Upgrades required every frontend to redeploy. Affiliate onboarding was manual. Implementations diverged. Users were locked into rigid account structures.

AccountLayer consolidates all of that into one system: a single Diamond proxy, a standardized affiliate registry, virtual accounts with deterministic addresses, and configurable isolation strategies.


Account Hierarchy

Accounts are organized into two layers.

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 might have one SubAccount for conservative trades and another for aggressive strategies.

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

Both SubAccounts and VAs are virtual addresses -- deterministic CREATE2-style addresses with no deployed contract behind them. The AccountLayer manages their state centrally.

Fund Flow

  1. User to SubAccount -- The user deposits collateral, credited to the SubAccount's balance on the Symmio core.

  2. SubAccount to VA -- The user manually transfers margin to a VA before trading. This can target an existing VA (addMargin) or pre-fund the predicted next VA address (addMarginToNextVA) before it exists.

  3. VA to SubAccount -- Automatic when the VA's last position closes. Users can also manually pull margin back via removeMargin, which requires a UPNL signature to prove the VA remains solvent.


Isolation Types

The isolation type, chosen when creating a SubAccount, determines how VAs are created and managed during trading.

POSITION -- One VA per Trade

Every quote gets its own VA. Maximum isolation: each position has separate margin, and a liquidation on one cannot affect any other. When the position closes, the VA is destroyed and funds return to the SubAccount.

MARKET -- One VA per Market

Positions on the same market share a VA. All BTC-USD trades go to one VA, all ETH-USD trades to another. Different markets remain fully isolated.

Without Single VA Mode (the default), each sendQuote creates a new VA even if one already exists for that market. With Single VA Mode enabled, subsequent quotes for the same market route to the existing active VA. This setting can be toggled when no VAs are active on the SubAccount.

MARKET_DIRECTION -- One VA per Market + Direction

Like MARKET, but also separates by direction. All BTC longs share one VA; all BTC shorts share another.

CUSTOM -- Full Manual Control

No automatic VA creation. Trades can execute directly through the SubAccount (no isolation), or through manually created VAs with any configuration the user chooses. VAs that track quoteIds are still automatically cleaned up when their last position closes.


The Trading Flow

Here is how a trade moves through the system, end to end:

  1. Pre-fund -- The user transfers margin to the target VA address. For a new trade, this means calling addMarginToNextVA with the predicted next VA address (which the ViewFacet.predictNextVirtualAccountAddress helper can compute). For an existing VA, addMargin works.

  2. Send quote -- The user submits a sendQuote through _call(). The AccountLayer intercepts it:

    • On a non-CUSTOM SubAccount: creates or reuses a VA based on the isolation type, routes the quote through it, and tracks the quoteId.

    • On a CUSTOM SubAccount: executes directly, no VA involved.

    • On a VA directly: enforces isolation rules (POSITION allows max 1 quote; MARKET/MARKET_LONG/MARKET_SHORT enforce symbolId and direction match), then executes and tracks the quoteId.

  3. Position opens -- The Symmio core calls onOpenPosition (a no-op on the AccountLayer side).

  4. Position closes -- The Symmio core calls onClosePosition. The AccountLayer removes the quoteId from the VA. If the VA has no remaining quoteIds:

    • All remaining margin is deallocated (via zeroUpnlDeallocate -- zero unrealized PnL, no signature needed)

    • All funds are transferred back to the parent SubAccount

    • The VA address is pushed into a recycling pool

    • The VA deletion hook fires (if configured)

    Cancellations and liquidations are also handled automatically. See Virtual Account Lifecycle Hooks for the full flow including partial closes, liquidation deferral, and settlement recovery.

The user never manages VA lifecycle manually (unless using CUSTOM isolation). Funding is always manual.


Affiliate System

Affiliates are frontends and integrators that bring users to the protocol. The AccountLayer manages their full lifecycle: registration, approval, fee splitting, hooks, and delegated actions.

Registration

  1. Anyone can request registration, providing a name, brand color, admin address, fee stakeholder configuration, and which Symmio core(s) to use.

  2. A protocol APPROVER_ROLE holder reviews and approves. On approval, an AccountManager proxy is deployed (deterministic CREATE2), a fee distributor address is generated, and the affiliate is registered on each allowed Symmio core.

  3. The affiliate is active. Users can create SubAccounts under it.

State transitions: NONE -> PENDING -> ACTIVE <-> PAUSED. Pending registrations can be cancelled or rejected, which deletes the data entirely (resetting to NONE).

The AccountManager

Each affiliate gets an AccountManager contract whose primary purpose is migration convenience. It provides a nearly identical API to the old MultiAccount contracts -- create accounts, deposit, withdraw, execute trades, list accounts -- so frontends can swap contract addresses with minimal code changes. Under the hood, it authenticates the caller (by temporarily setting globalSigner on the AccountLayer) and proxies the call through. There is no reason to use it directly beyond backward compatibility.

Fee Distribution

Trading fees accrue under a virtual fee distributor address on the Symmio core. When an affiliate registers, they configure stakeholders -- a list of addresses and percentage shares (e.g., frontend operator 70%, referral partner 20%, protocol 10%). All shares including symmioShare must sum to exactly 1e18.

Any stakeholder receiver or DISTRIBUTOR_ROLE holder can trigger fee claims. The system withdraws accumulated fees from the core, splits them according to configured shares, and transfers each portion to the respective recipient.

Fee changes are two-step: the affiliate admin requests an update, then a protocol approver confirms it.

Hooks

Affiliates can attach hook contracts to account lifecycle events:

Event
Trigger

Account creation

A new SubAccount is created

VA creation

A new VA is created (auto or manual)

VA deletion

A VA is cleaned up (last position closed)

SubAccount deletion

A SubAccount is removed

Call execution

_call is invoked on an account (fires after all calls complete)

Hooks preserve the customization power frontends had when they owned their MultiAccount contracts -- NFT minting on signup, cashback, auto-binding to a PartyB, analytics tracking. During execution, hooks can call back into the Symmio core via CoreFacet.executeForAccount(callData), gated by two conditions: hookContext.isActive must be true (only during hook execution), and the function selector must be in hookAllowedSelectors[affiliate] (controlled by SETTER_ROLE).

Audit note: Hooks are unsandboxed external calls. A malicious hook can revert and block the operation (griefing) or consume excessive gas. The signer clearing mechanism (see Security below) prevents impersonation, and hookAllowedSelectors limits callback scope, but hooks can still affect liveness.

Delegated Actions

Affiliates can execute protocol-level operations through callAsAffiliate, which lets the affiliate admin (or authorized operators) call whitelisted functions on the Symmio core with the affiliate's identity. Useful for administrative tasks like fee configuration.

Express Deposits

Express deposits let affiliates split user deposits to maintain instant-withdrawal liquidity. A deposit is divided by the affiliate's configured express rate: the real portion goes to the Symmio core as collateral, the virtual portion goes to a Virtual Provider that credits the user immediately. See Express Deposit for details.


Security Model

The Global Signer Pattern

The AccountLayer authenticates users through a globalSigner variable instead of relying on msg.sender directly. This is the most security-critical mechanism in the system:

Signer Clearing

Every external call -- hooks, ERC20 transfers, virtual provider callbacks -- is wrapped to prevent callback attacks:

The same pattern is used in LibAccountLayerSafeERC20 for token transfers and in callHook() (with the additional step of setting/clearing HookContext).

Without this, a malicious hook or token contract could re-enter the AccountLayer during an external call and, since getSigner() would still return the user's address, execute operations as the user.

Access Control

Role
Controls

DEFAULT_ADMIN_ROLE

Can grant/revoke any role and assign role admins. Does not implicitly hold other roles.

APPROVER_ROLE

Activate affiliates, approve fee updates

SETTER_ROLE

Core whitelists, hook/call allowed selectors, AccountManager implementation, fee receiver

PAUSER_ROLE

Pause operations (affiliate admins can also pause their own affiliate)

UNPAUSER_ROLE

Unpause operations

SIGNER_SETTER_ROLE

Set globalSigner -- granted to each AccountManager during affiliate approval

DEPLOYER_ROLE

Defined but unused; AccountManager deployment is handled inside approveAffiliate

DISTRIBUTOR_ROLE

Trigger fee distribution

INSTANT_LAYER_ROLE

Bypass ownership checks for InstantLayer batched execution

Role admins are managed via setRoleAdmin(user, role, status). A DEFAULT_ADMIN_ROLE holder is implicitly a role admin for all roles.


Legacy Migration

Frontends on the old MultiAccount system can import existing accounts into the AccountLayer as SubAccounts with CUSTOM isolation. Ownership is preserved (only the actual owner can import), double-import is prevented, and imported accounts gain access to all AccountLayer features.


Architecture Reference

Facets

Facet
Responsibility

CoreFacet

SubAccount/VA lifecycle, deposits, trade execution routing, hook callbacks

AffiliateFacet

Affiliate registration, fee config, hooks, operators, express config

MarginFacet

Margin transfers between SubAccounts and VAs, emergency recovery

ViewFacet

Read-only queries for accounts, affiliates, addresses, predictions

ControlFacet

Role management, pause control, core whitelisting, allowed selectors

SymmioHookFacet

Receives callbacks from Symmio core for automatic VA cleanup (see VA Lifecycle Hooks)

Storage Layout

Storage Contract
Slot Key
Contents

AccountStorage

keccak256("diamond.standard.storage.accountlayer.account")

SubAccount/VA data, nonces, globalSigner, AccountManager bytecode

AffiliateStorage

keccak256("diamond.standard.storage.accountlayer.affiliate")

Affiliate configs, fee details, hooks, operators, hookContext

AccountLayerStorage

keccak256("diamond.standard.storage.accountlayer")

RBAC (hasRole, roleAdmins), globalPaused

Reentrancy Guard

keccak256("diamond.standard.storage.accountlayer.reentrancy")

Reentrancy status flag

Deterministic Address Generation

SubAccounts, VAs, and fee distributors are virtual addresses (no deployed contracts). AccountManagers are real CREATE2 deployments. All follow the CREATE2 formula: keccak256(0xff, deployer, salt, initCodeHash).

Cross-Contract Permissions

Role on Symmio Core
Why

SIGNER_ADMIN_ROLE

Set signer context when executing operations on behalf of SubAccounts/VAs

AFFILIATE_MANAGER_ROLE

Register affiliates and set fee collectors on the core

BALANCE_SETTLER_ROLE

Transfer funds between VAs and parent SubAccounts during cleanup

The InstantLayer requires INSTANT_LAYER_ROLE on the AccountLayer to bypass ownership checks during batched template execution.

Last updated