# Vision

The world's first on-chain CEX.

Despite cryptocurrency being an industry built upon the principle of decentralization, the current state of cryptocurrency trading presents a stark contrast to this vision. On average, around 90% of spot exchange volume occurs on centralized exchanges such as Binance and Coinbase. Centralized exchanges are currently favored by traders of all archetypes due to their lowered barrier of entry, speed, and higher liquidity. On the contrary, current decentralized exchanges suffer from high transaction fees, low liquidity, and a lack of user-friendly interfaces, especially for those unfamiliar with blockchain technology.

Crystal seeks to bring the CEX experience on-chain, facilitating seamless trading across highly liquid markets between any tokens. Enabled by Monad, a modern EVM-compatible L1, Crystal's fully on-chain order book introduces the capital efficiency and lower fees of CEXs to the decentralized world without compromising on security or composability. By providing a superior trading experience enhanced by the benefits of permissionless and self-custody exclusive to decentralized exchanges, Crystal aims to surpass centralized exchanges in becoming the premiere venue for all cryptocurrency trading.


# Overview

See what the world's first on-chain CLOB DEX has to offer.

## What is Crystal?

Crystal is a fully on-chain central limit order book exchange (CLOB), created to bridge the gap between the transparency, security, and permissionless nature of decentralized exchanges (DEXs) and the speed, capital efficiency, and lower fees of traditional centralized exchanges (CEXs).&#x20;

Built on the Monad blockchain, Crystal allows for a seamless CEX-like trading experience while remaining fully self-custodial, permissionless, and decentralized. Crystal is also fully composable and can serve as spot liquidity for any DeFi app as a substitute/addition to automatic market maker (AMM) DEX liquidity.

## Key Features

* Crystal's CLOB architecture allows for lower fees and better capital efficiency than traditional AMM DEXs, leading to consistently better quotes for traders.
* All activity on Crystal, including but not limited to trade placement and settlement, are executed entirely via smart contracts on the blockchain, eliminating the need for any centralized intermediaries.
* Crystal's native launchpad allows anyone to permissionlessly create new tokens and markets. Tokens start on the bonding curve and transition to the dual AMM/CLOB architecture, meaning no external CEX listings are required for a newly launched token to trade with orderbook liquidity.
* Crystal takes advantage of Monad's lightning-fast transactions and near-zero gas fees to ensure a seamless trading experience. Crystal's scalability is only bound by the capacity of it's underlying blockchain, which can process up to 10,000+ transactions per second.
* Unlike hybrid order book exchanges which process trades off-chain and settle them on-chain, Crystal is composable with other DeFi applications, functions without any external dependencies, and does not require deposits/withdrawals to a third-party network/custodian.
* Crystal's smart contracts are immutable, and there are no privileged parties. All trading and market making is done on an even playing field and accessible to all.

Read our whitepaper: <https://crystal.exchange/whitepaper.pdf>


# Onboarding

Everything you need to know to begin trading on Crystal.

## Wallet Setup

To interact with Crystal, you will need either a mobile or browser extension wallet. If you do not already have one, [Rabby](https://rabby.io/), [MetaMask](https://metamask.io/), [Rainbow](https://rainbow.me/), or [Backpack](https://backpack.app/) is recommended, although any EVM-compatible wallet works. Install and set up your wallet of choice if you have not done so already, and be sure to save your seed phrase offline in a secure location.

Once you have a wallet, you need to fund it with assets on the Monad network. If you have assets on other EVM networks, you can bridge them to Monad. If not, you can purchase Monad tokens and withdraw them to your on-chain wallet. Ensure you have some native Monad tokens at all times to cover network fees.

## Quick Start

### 1. Connect your wallet

1. Navigate to <https://app.crystal.exchange>. Make sure you are on the correct URL, we recommend bookmarking the website to avoid phishing links.
2. Click the "Connect Wallet" button in the top-right corner.
3. Select your preferred wallet from the popup.
4. Approve the connection in your wallet.

### 2. Place a trade

Crystal is primarily a spot exchange, allowing users to buy and sell any listed asset. Unlike derivatives exchanges, spot trading involves the actual transfer of ownership of the underlying asset, and does not involve liquidations or leverage (yet).

1. Direct yourself to the Trade page and ensure your wallet is connected. Select the swap tab to swap any two tokens, limit to buy or sell tokens at a specified price, and send to transfer tokens to any other address on the network.
2. Select your input and output tokens.
3. Enter your desired input or output amount.
4. When placing a limit order, enter a limit price, and enable add liquidity only if you want to ensure your order executes as a maker order. When placing a swap, set a slippage limit or use the default (0.5%) and enable partial fill if you would like your order to fill until the slippage limit, otherwise your order will revert if the slippage limit is hit. For more information, see [Order Types](/spot-trading/order-types).
5. Click the swap button and confirm the transaction in your wallet. If you placed a limit order, you can view the fill status in the order center located at the bottom of the screen.

### 3. View your orders/balances

Your token balances can be viewed on the portfolio page, which includes a graph of your portfolio's value alongside several other statistics. You can view and manage your orders, trade history, and order history in the order center located near the bottom of the screen.

To learn how to customize Crystal's interface to best suit your trading preferences, check out [Customization](broken://pages/natFO40ghTykU69OJVMw).


# AMM vs CLOB

Crystal is the most versatile and efficient decentralized CLOB.

## What is an AMM ? (Automated Market Maker)

Historically, most decentralized exchanges have relied on an automated market maker (AMM) architecture due to its simplicity and low performance requirements. This is because the pricing logic of AMMs exists entirely onchain via mathematical equations, meaning liquidity providers do not have to submit transactions to update quotes. Additionally, the simple and straightforward "deposit and earn" model has attracted many retail investors due to its simplicity and low barrier of entry to liquidity provision.

The most widely used AMM formula is `x * y = k` which establishes a price range for two tokens based on their balances in the liquidity pool, also known as the constant product AMM. When the token supply of Token X increases, the supply of Token Y decreases proportionally to maintain the constant product K. [Uniswap](https://uniswap.org) V2 is a notable example of a constant product AMM, and it continues to retain a high market share in part due to the effectiveness of this simple algorithm. AMMs are especially optimal for long tail assets that market makers would be unlikely to quote as the algorithm guarantees assets are tradeable no matter the market conditions.

<figure><img src="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2F4FZopqPBjtsJzo0kVLfa%2Fimage.png?alt=media&amp;token=0d5b09c6-8b21-431c-8548-270a975743dc" alt="" width="375"><figcaption><p>Visualization of a constant product AMM curve</p></figcaption></figure>

### Impermanent Loss&#x20;

However, AMMs have many drawbacks. When the price of an asset changes on a different exchange, the price on the AMM remains the same, and arbitrage bots can profit from the stale quotes at the expense of liquidity providers. Additionally, when the price of a market drifts away from the initial price, liquidity providers experience a phenomenon known as impermanent loss, in which they would underperform simply holding both assets in a 50/50 proportion. Finally, AMMs must charge high fees to compensate for these two factors, and their mathematical curve-based quoting logic means traders will experience significant price impact on large trades as well. The majority of AMM liquidity loses money and/or is subsidized via token incentives.

### Low Capital Efficiency

Compared to orderbook exchanges, traditional AMMs require much larger amounts of capital to achieve the same liquidity. This is because a significant amount of AMM liquidity sits stale at both ends of the curve, only becoming of use when the price moves to an extreme. Because of this, at any given moment only a fraction of liquidity is actually active within reasonable levels of price impact to be traded against. On the other hand, on orderbook exchanges market makers actively manage their positions by setting specific buy/sell prices close to the mid price, leading to much more effective capital usage.

Learn more about AMMs: \[[Coinbase](https://www.coinbase.com/learn/advanced-trading/what-is-an-automated-market-maker-amm)] \[[Chainlink](https://chain.link/education-hub/what-is-an-automated-market-maker-amm)]

***

## What is a CLOB? (Central Limit Order Book)

A central limit order book (CLOB) is a form of market architecture where buy and sell orders are matched according to price-time priority. Orderbooks are the dominant market architecture in traditional financial markets due to their versatility and efficiency. Contrary to AMMs, orderbooks allow market makers a lot more flexibility in how they want to quote as they can place orders at individual price points. They are also much more capital efficient as useres only place orders at prices they want to trade at.

Learn more about CLOBs: \[[Wikipedia](https://en.wikipedia.org/wiki/Central_limit_order_book)] \[[Nasdaq](https://www.nasdaq.com/articles/demystifying-the-central-limit-order-book-clob-everything-you-need-to-know)]

### How Do CLOBs Work?

If a trader wants to buy 1 Ethereum when it reaches $3200, they place a limit order to the order book. If a seller is willing to sell 1 Ethereum at $3200, the two orders will get matched, and the trade will be executed. If no seller is found or available at that price, the order will remain in the book until a matching seller is found.&#x20;

<figure><img src="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2Fqx0vey03KquRm0xUkMPT%2Fimage.png?alt=media&amp;token=f715d4b5-fc06-4982-9224-8905f3d34201" alt="" width="342"><figcaption><p>Crystal's Orderbook</p></figcaption></figure>

Crystal utilizes an orderbook architecture due to the benefits stated above. On Crystal, the order book is updated live so that one can visualize market depth and liquidity in real time. With superior capital efficiency and more competitive quotes, Crystal is the first step to bringing onchain markets to parity with traditional financial markets in terms of speed and performance.


# Order Types

Utilize a variety of order types to enhance your trading experience.

* **Market Order:** An order that immediately executes at market price by matching against existing limit orders. By default, swaps are executed as market orders.\
  \
  **Options:**

  * Complete Fill: A complete fill order will revert if the output amount is insufficient due to slippage. It is comparable to swaps through popular AMM exchanges such as Uniswap and allows for trades across multiple markets.
  * Partial Fill: A partial fill order swaps until the slippage limit is hit, and any unfilled portion is canceled. When enabled, the slippage limit functions as a minimum price rather than a minimum output amount, meaning the percent slippage setting also limits price impact. Partial fills can only be done on individual markets.

* **Limit Order:** An order that adds liquidity to the order book and is executed at a specified price or better. Limit orders on Crystal have no expiration date, and orders at the same price are filled first in, first out. Unlike other decentralized limit order protocols, limit orders on Crystal are guaranteed to fill if the limit price is crossed. Limit orders can only be placed on individual markets.\
  \
  **Options:**

  * Add Liquidity Only: Add liquidity orders will only execute as a maker order, and will revert if the limit price crosses the bid-ask spread.
  * Market to Limit (default): By default, limit orders with prices crossing the bid-ask spread will fill as a market order up to the limit price. The remaining unfilled portion will be added to the order book as a limit order at the limit price.

* **Trigger Order:** An order that triggers a market order once a set price is reached. (Coming soon)

* **TWAP Order:** An order that splits a larger order into many suborders executed over time. (Coming soon)

To learn more about the technical details behind each order type, see [Exchange Architecture](/information/exchange-architecture).


# Fees

Crystal’s fees are among the lowest in the industry, with the proceeds designed to be allocated towards protocol and community growth. Fees can be further lowered through the use of the referral program, resulting in one of the lowest effective taker fee rates in the industry (as low as 0.009%). While fees are essential to the growth of any protocol, lower fees allow for tighter spreads and incentivize volume, which in turn increases protocol revenue while also accelerating protocol growth. Gas fees are negligible as Crystal is built atop Monad, a blockchain where the average trade costs under a cent to process.

#### Maker Rebate

Limit orders that add liquidity to the order book may recieve a fee rebate. This is automatically processed when an order is filled and does not netheed to be claimed in any way. Stable pairs do not have any maker rebate, while standard and volatile markets carry a maker rebate of 0.005%.

#### Taker Fees

Taker orders, or orders that remove liquidity from the order book, are charged a fixed fee ranging from 0.01-0.035%. Generally, market orders on stable pairs are charged a fixed 0.01% fee, while standard pairs have a 0.035% taker fee. With the referral program, referrers receive a 10% (subject to change) commission of the original fee. For more information on the benefits provided by the referral program, see [Referral Program](/community/referral-program).

#### AMM Fees

When a market is created, the fee of the AMM can be either set to 0.25% or 1%. This fee directly goes towards liquidity providers, automatically compounds, and is not paid on trades that are matched against resting limit orders. This fee is taken into account when trades are routed between the orderbook and AMM, with the best execution price being prioritized.


# Customization

We provide multilingual support and 17 different UI configurations, allowing users to alter the position and visibility of all essential elements on the trade screen. All non-trade-related settings can be customized with the settings modal accessible through the settings button in the header.

<figure><img src="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2FkJlDSeYcrwXbADqJRmza%2Fimage.png?alt=media&amp;token=2b6d16e3-00a0-4777-a91b-7992c5ed1802" alt=""><figcaption></figcaption></figure>

{% tabs %}
{% tab title="General" %}

#### Languages

English, Spanish, Chinese (Simplified), Japanese, Korean, Russian, Indonesian, Vietnamese, and Filipino.&#x20;

<figure><img src="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2FTmWPOQY91VXAhThZQewB%2Fimage.png?alt=media&amp;token=4717f3fa-8d52-464d-bbcd-bfbedb94f7a5" alt="" width="563"><figcaption></figcaption></figure>
{% endtab %}

{% tab title="Trading Layout" %}

### Swap Modal Location

You can change the location of the Swap modal to be either on the left or right side of the screen.

### Order Book Location

You can change the location of the order book to be either on the left or right of the chart.

### Dynamic Component Resizing

The order book and order center can also be dynamically resized by dragging, and the chart will fill up the remaining space.

### Chart Outliers

By default, scam wicks in the chart due to MEV or low liquidity are hidden. This can be disabled to get a more informed view of the market at the cost of visual experience.

<figure><img src="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2FmDhNgA5xcML34AXJPgbj%2Fimage.png?alt=media&amp;token=18f3ee9f-61d9-4877-aac3-c705941991a1" alt="" width="563"><figcaption></figcaption></figure>
{% endtab %}

{% tab title="Show/Hide" %}
The order book and order center can be shown/hidden, with the chart dynamically expanding to occupy the remaining space if any. Trade markers and order lines can also be shown/hidden.

<figure><img src="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2FXrIDwCPuQd8tZcFDt84v%2Fimage.png?alt=media&amp;token=3e7ec1c3-6580-4ee9-98fc-b87571e878b0" alt="" width="563"><figcaption></figcaption></figure>
{% endtab %}

{% tab title="Notifications" %}

<p align="center">Audio notifications can be enabled on action success and order fill.</p>

<figure><img src="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2FOZefJsv8Tni8cm01mGkU%2Fimage.png?alt=media&amp;token=d39b3813-e7b5-4cbf-8e00-b0e6863b669d" alt="" width="563"><figcaption></figcaption></figure>
{% endtab %}

{% tab title="Keybinds" %}

<p align="center">Set keybinds to improve your execution.</p>

<figure><img src="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2FCEMe4QfGnnpaVvgOZhom%2Fimage.png?alt=media&amp;token=ba63c234-7501-46c5-8ebb-db221d408d5c" alt="" width="375"><figcaption></figcaption></figure>
{% endtab %}
{% endtabs %}

***


# Liquidity Provision

Let your assets earn for you.

If you have technical knowledge and want to provide liquidity as a market maker, check out [Market Making](/developer-resources/market-making). If not, you can still earn by depositing into a vault or AMM liquidity pool.

On markets with a corresponding AMM pair, you can earn swap fees by depositing a share of both assets into the liquidity pool through the Earn page.


# Vaults

Deposit into the vault to earn 3x Crystals and a share of $3,750 in MON rewards.

A Crystal vault is a pool of quote and base assets that one operator trades on a single Crystal market. Depositors receive vault shares which are a proportional claim on everything the vault holds.

Vaults work like Uniswap V2 liquidity positions: you deposit both assets at the vault's current ratio, receive shares, and redeem shares for your slice of both assets at any time after your lockup ends.

### How It Works

| Role       | What they do                                           | What they control                                    |
| ---------- | ------------------------------------------------------ | ---------------------------------------------------- |
| Operator   | Creates the vault, seeds it, places and cancels orders | Strategy, deposit pause, lockup, deposit cap, market |
| Depositor  | Deposits both assets, holds shares, withdraws          | When to enter and exit                               |
| Governance | Sets protocol-wide limits                              | Max lockup, max order count, minimum initial deposit |

Governance can only set global defaults. It cannot move vault funds or trade on a vault's behalf.

### 1. Deposit

Every deposit must include both the quote asset and the base asset. The vault takes the two amounts at its current quote-to-base ratio and refunds the excess of whichever asset you over-supplied. Set minimum amounts to protect against the ratio moving before your transaction lands.

Shares are minted proportionally: if your deposit adds 10% to the vault's balances, you receive 10% of the new total supply.

ETH can be deposited directly. The factory wraps it into WETH for you.

Use `previewDeposit` to see the exact amounts and shares before sending a transaction.

Deposits are rejected when:

* the vault is **locked** or **closed**;
* the vault holds only one of its two assets (fully skewed after trading);
* the deposit would exceed the vault's deposit cap, if the operator set one.

### 2. Lockup

Each vault has a withdrawal lockup, set by the operator and capped by governance. After you deposit, your shares are locked for that duration.

**Every deposit resets your lockup.** Adding to an existing position restarts the clock on your entire balance, not just the new amount.

The lockup is per address and applies only to withdrawals. Shares are non-transferable.

### 3. Withdraw

Burn shares and receive your proportional amount of both assets. The calculation uses the vault's total balances, including funds locked in open orders, so your claim on in-flight orders is honored.

If the vault does not have enough free balance to pay you, it reduces the operator's open orders to free the funds. How much it reduces depends on the vault's decrease behavior:

| Mode                 | Behavior on withdrawal                                                        |
| -------------------- | ----------------------------------------------------------------------------- |
| Decrease on withdraw | Every open order shrinks by your withdrawal percentage                        |
| Default              | Orders on the short side shrink only as much as needed to cover the shortfall |

Either way you receive your full proportional amount. Set minimum amounts to guard against the balances shifting before execution.

Use `previewWithdrawal` to see what a given number of shares is worth now.

### 4. Operator Rules

The operator must hold **more than 5%** of the vault's total shares at all times. Deposits that would dilute the operator below 5% revert. The operator cannot reduce below 5% except by exiting entirely, which closes the vault.

The operator trades by sending batches of order actions directly to the vault. The operator can also:

* pause and resume new deposits;
* set or change the deposit cap;
* shorten or lengthen the lockup, up to the governance maximum;
* switch decrease behavior;
* cancel all orders;
* move the vault to the canonical market for its pair, which cancels all orders first.

The operator cannot transfer depositor funds anywhere except into orders on the vault's market. Withdrawals are always proportional, so the operator's exit pays them exactly their share.

Vault shares follow the ERC20 interface for balance reads but cannot be transferred or approved.


# AMM Liquidity (LP)

Provide passive liquidity to a Crystal market's built-in constant-product pool and earn the pool's swap fee on every trade routed through it.

Every Crystal market has an orderbook and, when enabled, an AMM pool. The pool is a Uniswap V2-style constant-product pool (x · y = k) holding the market's quote and base assets. It lives inside the market contract, not in a separate pool contract, so a single taker order can fill against both the orderbook and the pool in one transaction.

Unlike a vault, nobody operates the pool. The curve sets the price and fills trades automatically.

### How It Works

|                 | Orderbook                   | AMM pool                             |
| --------------- | --------------------------- | ------------------------------------ |
| Who quotes      | Makers placing limit orders | The constant-product curve           |
| Liquidity shape | Discrete price levels       | Continuous across all prices         |
| Provider action | Place and manage orders     | Deposit once, hold LP tokens         |
| Provider earns  | Maker rebate on fills       | 0.25% of every swap through the pool |

### 1. How Trades Route

A taker order walks the book one price level at a time. Before filling each level, the engine checks whether the pool offers a better price. If it does, the trade swaps through the pool just until the pool's price reaches that level, then fills the level, then moves on. The pool's price is compared to orderbook prices after adjusting for the maker rebate, so the comparison is on equal footing.

The result is that the pool fills the gaps between orderbook levels and absorbs volume when the book is thin. Takers always get the better of the two sources at every step.

Limit orders cannot be placed inside the pool's spread. A bid above the pool's ask price or an ask below the pool's bid price is rejected. This keeps the pool at or inside the best bid and ask at all times.

### 2. Add Liquidity

Deposit both assets at the pool's current reserve ratio. You specify the maximum of each asset you are willing to add; the pool takes the matching amount at the current ratio and leaves the rest in your wallet. Set minimum amounts to protect against the ratio moving before your transaction lands.

You receive LP tokens proportional to your share of the reserves. If your deposit is 10% of the pool after adding, you hold 10% of the LP supply.

The first deposit into an empty pool sets the initial price. The pool rejects a first deposit whose implied price would cross the existing orderbook. A fixed 100,000 units of LP tokens from the first deposit are permanently burned, matching the Uniswap V2 minimum-liquidity rule.

Deposits are rejected while the pool is disabled for that market.

### 3. LP Tokens

LP tokens are standard ERC20 tokens with symbol CRY-V2, issued by the market contract itself. They are transferable. Whoever holds them can redeem them.

Your position's value is your share of the reserves. Because swap fees stay in the reserves, the value of one LP token in underlying assets rises with every swap through the pool.

### 4. Remove Liquidity

Burn LP tokens and receive your proportional share of both reserves. Set minimum amounts for slippage protection.

One constraint: a withdrawal cannot leave the pool's price crossing the orderbook. If it would, the pool keeps back just enough of one asset to hold its price at the best bid or ask and pays you the remainder. If that holdback exceeds your share, the transaction reverts. Rebalance the book or wait for it to move before retrying.

There is no lockup. You can withdraw in the same block you deposited.

### 5. Fees

Every swap through the pool pays 0.25% of the input, which stays in the reserves and accrues to LP holders. The protocol takes no cut of this fee.

Takers additionally pay the market's standard taker fee on the whole trade, including any portion filled by the pool. That fee goes to the protocol, referrers, and on launchpad markets the token creator. It does not go to LPs.

### 6. Launchpad Markets

When a launchpad token graduates, its bonding-curve proceeds are seeded into the new market's pool as the initial liquidity. Graduated markets trade like any other: orderbook plus pool.

### 7. Risks

* **Impermanent loss.** The pool rebalances toward the losing asset as price moves. If price moves a lot in either direction, holding the two assets would have been worth more than the LP position.
* **Adverse selection.** The pool fills whenever it is the cheapest liquidity available. It is the first to trade against informed flow.
* **Withdrawal holdback.** If the book has moved past the pool's price, part of your withdrawal may be retained or the withdrawal may revert until conditions change.

Pool reserves are full-range. There is no concentrated liquidity or price range selection.


# Launchpad

Launch coins from a bonding curve to a fully onchain hybrid AMM/CLOB exchange while earning creator fees.

Crystal Launchpad lets anyone create a token that can begin trading immediately.

New launchpad tokens start on a bonding curve. As people buy the token, its price rises automatically according to the curve. Once enough activity has occurred, the token **graduates** into a normal Crystal market with both an orderbook and AMM liquidity.

There is no separate migration step for users. Graduation happens automatically.

### How It Works

A launchpad token has two stages:

| Stage     | How it trades           | What users do                       |
| --------- | ----------------------- | ----------------------------------- |
| Launchpad | Bonding curve           | Buy and sell through the launchpad  |
| Graduated | Crystal orderbook + AMM | Trade like any other Crystal market |

#### 1. Create a token

Anyone can create a launchpad token by providing:

* a name;
* a symbol;
* metadata;
* a description; and
* optional social links.

Crystal creates both the token and the market it will eventually graduate into.

The creator does not need to manually create liquidity or deploy a separate market.

If native currency is sent while creating the token, it is immediately used to buy the newly created token.

#### 2. Trade on the bonding curve

Before graduation, users buy and sell against the launchpad's bonding curve.

The bonding curve automatically determines the token price based on how many tokens remain available.

As users buy:

* the token reserve falls;
* the native reserve rises; and
* the token becomes more expensive.

As users sell:

* tokens return to the curve;
* native currency leaves the curve; and
* the token becomes cheaper.

This gives every newly created token an immediately available market without requiring an external liquidity provider.

### Token Supply

Each launchpad token has a fixed supply of:

```
1,000,000,000 tokens
```

Of that supply:

```
800,000,000 tokens
```

are available through the launchpad curve before graduation.

The remaining:

```
200,000,000 tokens
```

are reserved for the AMM liquidity used by the graduated Crystal market.

Crystal also uses additional **virtual reserves** for pricing. These virtual reserves affect the shape of the bonding curve but are not extra circulating tokens.

### Fees

Launchpad trades pay a fee.

The fee is split between:

* the token creator; and
* the Crystal protocol.

The creator's share is controlled by the launchpad configuration.

After graduation, trading uses the fee settings of the graduated Crystal market instead, while the creator fee remains but the fee split may change.

### Graduation

A token graduates once the bonding curve reaches its configured graduation point.

At graduation, Crystal automatically:

* activates the token's Crystal market;
* makes the pair available through normal Crystal routing;
* activates the market's AMM liquidity;
* enables normal orderbook trading; and
* transitions future trading to Crystal's standard market infrastructure.

No action is required from the token creator or holders.

A buy can even cross the graduation point and continue into the newly activated market in the same transaction.

### After Graduation

Once graduated, the token behaves like a normal Crystal market.

Users can trade through:

* normal swaps;
* the orderbook;
* market orders;
* limit orders; and
* the AMM.

The launchpad `buy` and `sell` interfaces also continue to work and automatically route through the graduated market.

### Liquidity After Graduation

Crystal prepares the token's initial AMM liquidity when the token is created.

The AMM starts with:

```
200,000,000 launchpad tokens
```

plus the corresponding native-side liquidity determined by the launchpad curve.

The LP tokens for this initial liquidity are permanently locked, so this initial market liquidity cannot simply be withdrawn by the token creator.

### Trading Across Graduation

A launchpad buy can span both stages.

For example:

```
User submits buy
      ↓
Part of buy fills on bonding curve
      ↓
Graduation threshold is reached
      ↓
Crystal activates the full market
      ↓
Remaining buy continues through the graduated market
```

This happens in one transaction.

From a user perspective, the transition is automatic.

### Quotes

Crystal provides quote functions for launchpad trades.

Use:

```
quoteBuy
quoteSell
```

to estimate a trade before submitting it.

Quotes account for:

* the bonding curve;
* launchpad fees;
* graduation; and
* the graduated market when applicable.

### Launchpad Events

Before graduation, launchpad trades emit:

```
LaunchpadTrade
```

When graduation occurs, Crystal emits the normal market initialization events.

After graduation, trades use the normal Crystal market events such as:

```
Trade
Fill
OrdersUpdated
Sync
```

depending on how the trade executes.

### Summary

The Crystal Launchpad is designed to take a token from creation to a fully functioning Crystal market automatically:

```
Create Token
    ↓
Bonding Curve
    ↓
Graduation
    ↓
Orderbook + AMM Market
```

The creator does not need to manually bootstrap a pool, migrate liquidity, or deploy a second market. Crystal handles the entire transition on-chain.

Content on the launchpad is entirely user created and is not endorsed by nor reflects the views of Crystal Labs and/or any its employees.


# Fee Configuration

The current Crystal deployment configures launchpad trading with a **1% bonding-curve fee**:

Of the fee collected before graduation:

* **10%** goes to the token creator.
* **90%** goes to the protocol.

After graduation, the token trades as a normal Crystal market with:

| Setting           |      Value |
| ----------------- | ---------: |
| Taker fee         |  **0.03%** |
| Maker rebate      | **0.005%** |
| Creator fee split |    **50%** |

The deployment also sets Crystal's global referral commission to **10% of the taker fee**. This applies to supported post-graduation trades that include a referrer only.


# Spectra

Spectra offers a unified, high-performance interface for monitoring launchpad tokens across multiple platforms. Track Crystal launchpadnad.fun , and many upcoming tokens from Flap, a Something<br>

<figure><img src="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2FJQErPpRjO5jBAsiao1jj%2Fimage.png?alt=media&amp;token=f92c0204-f5db-4d8e-9ff7-ac77d5246fda" alt=""><figcaption></figcaption></figure>

### Filters

<figure><img src="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2F7rryFVkB9WpHtJpG9rEO%2Fimage.png?alt=media&amp;token=098d3c98-a5df-434e-bd57-f6a7068a6305" alt="" width="375"><figcaption></figcaption></figure>

<table><thead><tr><th>Audit (filters)</th><th>Description</th><th data-hidden></th></tr></thead><tbody><tr><td>Age</td><td>Time since the token was launched or first deployed.</td><td></td></tr><tr><td>Holders</td><td>Total number of unique wallets holding the token.</td><td></td></tr><tr><td>Pro Traders</td><td>Number of wallets flagged as experienced or high-performing traders that hold the token.</td><td></td></tr><tr><td>Top 10 Holders %</td><td>Top 10 Wallets based on largest holdings.</td><td></td></tr><tr><td>Dev Holders</td><td>Tokens held by the deployer or known developer-linked wallets.</td><td></td></tr><tr><td>Snipers Holdings</td><td>Amount of supply held by wallets that bought instantly at launch.</td><td></td></tr><tr><td>Insider Holdings</td><td>Supply held by wallets identified as insiders.</td><td></td></tr></tbody></table>

<table><thead><tr><th>Metrics (filters)</th><th>Description</th><th data-hidden></th></tr></thead><tbody><tr><td>Market Cap </td><td>Current total valuation of the token based on price × supply.</td><td></td></tr><tr><td>Volume 24h</td><td>Total trading volume in the last 24 hours.</td><td></td></tr><tr><td>Global Fees Paid</td><td>Total fees accumulated from all the swaps for this token, useful for filtering washtrades.</td><td></td></tr><tr><td>Buy Transactions</td><td>Number of buy trades</td><td></td></tr><tr><td>Sell Transactions</td><td>Number of sell trades</td><td></td></tr><tr><td>Price</td><td>Current token price</td><td></td></tr></tbody></table>

<table><thead><tr><th>Socials (filters)</th><th>Description</th><th data-hidden></th></tr></thead><tbody><tr><td>Has Website</td><td>Provides a link to a verified website</td><td></td></tr><tr><td>Has Twitter/X</td><td>Provides a link to a twitter profile</td><td></td></tr><tr><td>Has Telegram</td><td>Provides a link to a telegram groupchat</td><td></td></tr></tbody></table>

### Search (top left)

You can search by name or address.

#### Features:

Default sort is **1 hour volume** but you can also sort by time, marketcap, and number of holders.

Preset settings for quickbuy are also available.

<figure><img src="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2FMyspI6haoRXjCler8IYJ%2Fimage.png?alt=media&amp;token=91db4311-cd4e-4f31-a9ba-c832a87d44f0" alt=""><figcaption></figcaption></figure>

You can also enable 1 click trading on top right.<br>

<figure><img src="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2Fh6Ud8oXaL805NS3lAgLo%2Fimage.png?alt=media&amp;token=3d839909-73f4-4ecc-846a-139baa3191de" alt=""><figcaption></figcaption></figure>

### Display Settings (per token row)

<figure><img src="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2FABaSKvtZl6CWRPo5XqjD%2Fimage.png?alt=media&amp;token=cab2b2e4-71dd-49a2-95dd-e1c12fb72013" alt=""><figcaption></figcaption></figure>

### Alerts

Change different sound alerts for new tokens listed per column.

<figure><img src="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2FHFM2PC2rVLPwRm7ug4zs%2Fimage.png?alt=media&amp;token=f3ccc6ec-405b-4c1e-8d20-ba1c14d8d9ba" alt=""><figcaption></figcaption></figure>

### Trade Settings

You can change quickbuy amount and presets (slippage and priority fees).

<figure><img src="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2FTnJC43306s1FgxFPPfDW%2Fimage.png?alt=media&amp;token=9dbdbc3c-fc06-4f95-9007-c765c5775f1b" alt=""><figcaption></figcaption></figure>

### Fees

1% trading fee.\
Portion of the fee is rebated back to the user/referrer depending on their rewards tier.&#x20;

Additionally, once a token graduates, 25% of the fees collected rebates to the token creator.


# One-click trading

Trade on any market without signing a wallet prompt.

One-Click Trading (1CT) works across all trades including spot order book, trading terminal, vault deposits, and V2 AMM Liquidity Pools.&#x20;

Every order type is supported under 1CT: limit, market, market-to-limit, batch, replace, and cancel.

### How It Works

Enabling 1CT creates a session key in your browser and authorizes it on-chain to trade on your behalf. Your wallet signs once to grant that authorization. After that, the session key signs your orders locally and submits them directly.

### Enable/Disable

Toggle 1ct in the trading terminal and sign the authorization with your wallet.

<figure><img src="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2F6RQr7YtibChsBsF7RRGP%2Fimage.png?alt=media&amp;token=076fb40d-8d40-4e64-8e66-9dc671d1611d" alt=""><figcaption></figcaption></figure>


# Trackers

Add and monitor wallets you want to track.

{% tabs %}
{% tab title="Wallet Manager" %}

#### Add or import wallets

<figure><img src="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2FxCko3tOiU11sKs2tCgV1%2Fimage.png?alt=media&amp;token=1dff4a0c-2829-4728-ab97-63b62af902d5" alt=""><figcaption></figcaption></figure>
{% endtab %}

{% tab title="Live Trades" %}
50 most recent trades are displayed.

<figure><img src="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2F8LbRLGLthx8Ymwp08UFJ%2Fimage.png?alt=media&amp;token=bf00f499-fad1-423d-9444-ea714166ec6d" alt=""><figcaption></figcaption></figure>

Note: You can also hover over the trades to quickbuy the token.
{% endtab %}

{% tab title="Socials" %}
3 Tabs: Alert, Tweets, My List\
\
Alert shows a default list of tokens (sorted by most recent tweets), you can change and customize this page.\
\
Tweets tab shows the tweets from X handles you have added in My List\
\
My List shows the X/Twitter accounts you are tracking.
{% endtab %}
{% endtabs %}

A widget version of wallet tracker can be found in the bottom of app, any settings related changes made in one will be applied to the other.


# Portfolio

### Spot Portfolio

You can view all your portfolio activities in this page, including but not limited to: Balances, PNL graph, Open Orders, hooldings distribution, etc.&#x20;

#### Referrals

You can **create your own referral code** or **use your friends'** to earn **trading fee rebates** and **Crystals**. The accumulated points are claimed with a single click.

<figure><img src="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2FPmnONdaTM26Dloi6muI6%2Fimage.png?alt=media&amp;token=6abcb781-1d67-42cc-9b1e-d239683ea46d" alt=""><figcaption></figcaption></figure>

***

### Sub-wallets

{% content-ref url="/pages/sTAHDPgRA4LxRHGhNPOy" %}
[One-Click Trading](/trading-terminal/one-click-trading)
{% endcontent-ref %}

Unified dashboard for managing all your sub-wallets. Sub-wallets are generated as isolated accounts under a user's main wallet. Each Sub-wallet has its own balance, activity logs, and permissions, while still remaining linked to the main account for unified control.

<figure><img src="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2FQkLhJiY70mEqiD2eL8gV%2Fimage.png?alt=media&amp;token=cc69a532-bc15-47a7-9ab8-050a85153e32" alt=""><figcaption></figcaption></figure>


# One-Click Trading

### What are subwallets?

Subwallets allow users to create multiple *child* wallets under a single main wallet, enabling structured separation of assets, strategies, or trading activities without needing to manage entirely independent wallets. Instead of juggling numerous seed phrases or browser extensions, Subwallets provide an organized way to segment funds inside one master wallet environment.

#### Features

1. Create multiple sub-wallets (Up to 10 per user).
2. Distribute funds across sub-wallets.
3. Switch between wallets you'd like to trade on with a single click.
4. Name and customize individual sub-wallets at your will.

***

### 1. Head over to Portfolio and select Wallet tab

<figure><img src="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2FPQ5sNpeQadmeVUnsbKMz%2Fimage.png?alt=media&amp;token=2d4f6c71-9af6-474c-a45b-715188865e78" alt=""><figcaption></figcaption></figure>

#### Drag desired wallets into Source Wallets and Destination Wallets.

<figure><img src="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2FP91rzobBDjIrHXjVIKvE%2Fimage.png?alt=media&amp;token=4a2dd4df-5aa7-434c-9814-cb7c3eb2aa10" alt=""><figcaption></figcaption></figure>

asd

***

#### Key Benefits

* **Structured Account Management:**\
  Users can create multiple Subwallets under a primary wallet to keep activities separate (e.g., *High-Risk Trading*, *Farm Wallet*, *Long-Term Holdings*, *Bot Execution Wallet*, etc.).
* **Centralized Control Panel:**\
  View all Subwallets from a single dashboard — check balances, open positions, and transfers without switching between wallets or tabs.
* **Fast Switching Between Subwallets:**\
  Switch active Subwallets instantly, enabling quick trades or transfers without re-connecting or re-authenticating a wallet.
* **Custom Names & Tags:**\
  Label Subwallets by purpose, strategy, or trader profile for easier navigation and reporting.\
  \
  \ <br>

safsf

***

<p align="center"><img src="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2FasxMxuLkAc9IBUdMEn0f%2Fimage.png?alt=media&amp;token=5b008abe-859c-4ab4-9630-296becc8e7e9" alt=""><br>dsgsg</p>

***


# Crystals

Trade to earn.

Crystals are a seasonal incentive program that rewards users for trading, providing liquidity, referring new users, and contributing to the growth of the Crystal ecosystem.\\

\
Throughout each weekly rewards period, eligible activity on Crystal contributes toward a user’s rewards score. At the end of each period, 1,000,000 Crystals are distributed among eligible participants based on their relative contribution during that week.\\

\
Crystals accumulate throughout the season and will be used to determine users’ eligibility for a share of future Crystal token incentives.

### Earning Crystals

Users can contribute toward weekly rewards through activity across the Crystal ecosystem, including spot trading, launchpad trading, market making, liquidity provision, referrals, and other eligible activities.

Different forms of activity may be weighted differently based on their contribution to the protocol. Crystal may also announce temporary boosts or incentive campaigns for selected products or programs.\\

\
Contribution earned during a weekly period is used to determine each participant’s share of that week’s Crystal distribution. Larger contributions generally result in larger allocations, although rewards may incorporate mechanisms designed to encourage and reward broader participation.

### &#xD;Rewards Structure

Each weekly rewards period ends on Wednesday at 12:00 AM EST.\\

\
After the period closes, eligible activity is reviewed and the week’s 1,000,000 Crystal allocation is calculated, with balances for the completed period being finalized no later than Friday at 11:59 PM EST. Once Crystals have been distributed, the week's allocations become permanent and will not be reduced or retroactively recalculated except in the case of fraudulent participation.\\

\
Crystal may adjust activity weightings, qualifying activities, or other reward parameters for future periods as the program develops.

### &#xD;Weekly Status

At the end of each weekly period, participants are also assigned a status based on their relative placement among eligible users, from bronze to diamond.\\

\
Users retain earned status badges after the corresponding rewards period has ended, but may subsequently lose them in further . If users fall directly on a ranking boundary or tie for a qualifying position, they may receive the higher applicable status.

### &#xD;Referral Rewards

Users can earn additional Crystals by referring new participants to Crystal, with rewards unlocked as referred users become active within the ecosystem.\\

\
At the initial milestone, the referred user may also receive a welcome reward. Higher milestones provide progressively larger rewards to the referrer.

### &#xD;Fair Participation

Activity associated with wash trading, self-trading, Sybil activity, self-referrals, coordinated manipulation, artificial liquidity activity, exploits, or other attempts to manipulate the rewards program may be excluded from rewards calculations or otherwise adjusted.\\

\
Crystal may review related accounts or activity when necessary to preserve the integrity of the program.


# FAQs

Your questions, answered.

#### What is Wrapped Monad?

Wrapped Monad is an ERC-20 wrapper of Monad, pegged 1:1 and redeemable with no fee at any time. This grants the token greater flexibility when used in DeFi, such as being supported by smart contracts designed to accommodate any ERC-20 token. Limit orders for Monad are filled using Wrapped Monad, as the underlying smart contract behind each of Crystal's markets is designed for ERC-20 tokens. To convert Wrapped Monad to Monad and vice versa, head to the swap tab and perform a 1:1 trade between the two tokens as you would with any other.

#### What sets Crystal Exchange apart?

Crystal combines the capital efficiency of a Central Limit Order Book with the permissionlessness and security of blockchain technology. Crystal also seeks to improve upon existing exchange UX while avoiding custodial risks that centralized exchanges pose.

#### Why Monad?

Crystal is deployed solely on Monad, chosen for its best-in-class performance while maintaining a high degree of decentralization. Due to liquidity fragmentation, we do not foresee deployment on any other chains in the near future. Instead, integrations with third-party bridges will allow for seamless trading of cross-chain assets across all EVM-compatible chains.

#### How can I start trading?

To use Crystal, navigate to <https://app.crystal.exchange> with an EVM-compatible wallet installed and funds on the Monad blockchain.

For a more in-depth tutorial, please refer to [Onboarding](/spot-trading/onboarding).

#### Do I control my assets?

Yes! Crystal is non-custodial, meaning you retain full ownership and control of your assets at all times. Crystal does not require deposits and withdrawals like centralized and hybrid exchanges do.

#### What are the fees?

Crystal's fees are universally competitive across both CEXs and DEXs:

* **Maker Fee:** -0.005%
* **Taker Fee:** 0.01-0.035%

For additional details on fees and how they apply, please visit our [Fees](/spot-trading/fees) page.

#### Is Crystal safe to use?

Absolutely. Crystal's smart contracts are thoroughly audited, immutable, and operate on-chain without reliance on external dependencies.

#### Does Crystal Exchange require KYC?

No, KYC is not required as Crystal Exchange is entirely permissionless. We do not require or collect any personal information, and all you need is an EVM wallet to start trading.

#### Why are referral fee discounts claimable as rebates?

For external integrations, a fixed taker fee greatly simplifies output calculation and provides a singular case regardless of whether a referral code was used. If a direct fee discount were applied, this would alter the output amount of trades, introducing unnecessary complexity that strongly impacts composability.

#### How can I earn by providing liquidity?

Anyone is welcome to market make on an equal playing field, although it requires some technical knowledge and programming experience. In the future, you will be able to deposit into our earn vaults, in which experienced algorithmic traders can provide liquidity on your behalf.

If you still have questions, feel free to reach out through our [Discord](https://discord.gg/GzczPx8ng7).


# Referral Program

Invite friends, earn together.

Crystal’s referral program is an open, on-chain system that allows anyone to generate a personal referral link and earn rewards when new traders join the Crystal terminal. The program is designed to reward and empower creators, traders, and external integrations while growing the platform organically.

### How It Works

Any user can create a referral link through the terminal. Each wallet may have one active code at a time. Generating a new code replaces the previous one and makes the old one available for anyone else to claim. Traders may freely switch which code they use; codes do not persist across sessions, so bookmarking a preferred code is recommended.

When someone signs up or trades through your code, their activity is automatically tracked on-chain. Rewards accrue instantly and are always claimable directly in the referral tab.

### Benefits:

* **Fee Commissions:** A percentage of the fees generated by referred users, calculated before discounts. Rewards appear in real time and can be claimed at any moment.
* **Crystals Multipliers:** Both referrer and referee receive boosted multipliers in Crystal’s incentives program.
* **Instant Claims:** Fees/rebates earned as part of our referral program are instantly claimable in the referrals tab as the output token of each trade.

**Warning:** Self-referral is strictly prohibited and will lead to disqualification from all present and future incentive programs.

To protocols [integrating](/developer-resources/aggregator-defi-integration) Crystal's liquidity, such as aggregators, the referral program can be better described as a revenue-sharing program. Both off-chain interfaces and smart contracts can and are encouraged to pass their address to receive referral rewards. For protocols, no creation of a referral code is necessary, as referral rewards can be directed to any address.

For any questions or clarifications, feel free to reach out to our team through our [Discord](https://discord.gg/GzczPx8ng7). We’re here to help you make the most of this program!


# Links / Resources

Website: [https://crystal.exchange](https://crystal.exchange/)\
App: <https://app.crystal.exchange>\
X / Twitter: <https://x.com/CrystalExch>\
Discord: <https://discord.gg/CrystalExch>\
Github: <https://github.com/CrystalExch>\
Whitepaper: <https://crystal.exchange/whitepaper.pdf>\
Documentation: [https://docs.crystal.exchange](https://docs.crystal.exchange/)


# Brand Kit

{% tabs %}
{% tab title="Logo" %}

<figure><img src="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2Fs8wQ4SDHnYRHd25bXXdp%2Flogo.svg?alt=media&amp;token=b205f95f-431a-4e9a-9f3d-83671f84e1a4" alt=""><figcaption></figcaption></figure>

{% file src="/files/D3aPybyd2OLDsSoZFFMv" %}
Black Background
{% endfile %}

{% file src="/files/niIGyvzVGvUOyaApKLpN" %}
Transparent Background
{% endfile %}
{% endtab %}

{% tab title="Logo With Text" %}
{% file src="/files/LY2xijdaEm0jMP0Rjzru" %}
Transparent Background
{% endfile %}

{% file src="/files/wIE51scpD3tEdLG6Rt60" %}
Transparent Background
{% endfile %}

{% file src="/files/mxm3gUYT4jMSzEYu0xXA" %}
Transparent Background
{% endfile %}

{% file src="/files/fnOkHl010a75XoiVAT8x" %}
Transparent Background
{% endfile %}
{% endtab %}

{% tab title="Primary Color" %}
HEX: #aaaecf\
RGB: 170, 174, 207\
HSV: 233, 17, 81\
CMYK: 18, 16, 0, 19

<figure><img src="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2FGhoXd3jzgnzC1xNXt7pw%2Fimage.png?alt=media&amp;token=3f5174f0-72a7-4bca-ad20-9e67ed5d8433" alt=""><figcaption></figcaption></figure>
{% endtab %}

{% tab title="Logo Variants" %}
Colors:

{% file src="/files/o1LVvP8jSY8XfklaVohD" %}
Transparent Background
{% endfile %}

{% file src="/files/z1f9DzDeTkryIz1u9WjR" %}
Transparent Background
{% endfile %}

\
Sizes:

{% file src="/files/jmo9AnAGEO9Cy0IvrM74" %}
Transparent Background
{% endfile %}

{% file src="/files/wCbZFev4AnTtrS1xprq0" %}
Transparent Background
{% endfile %}

{% file src="/files/0aKDFRejDG7xF7ETxPVF" %}
Transparent Background
{% endfile %}

\
Square Margin:

{% file src="/files/feBAUkUpi0AtuqL7yt54" %}
Black Background
{% endfile %}

{% file src="/files/ar1pw3IIlSPlUnNDX02n" %}
Transparent Background
{% endfile %}

{% file src="/files/NwO7Jd9iSqjGpfB6d89B" %}
Transparent Background
{% endfile %}

{% file src="/files/qzXqMe6GIrErTxExVjls" %}
Transparent Background
{% endfile %}
{% endtab %}
{% endtabs %}


# Exchange Architecture

Learn the technology behind Crystal's next-generation on-chain order book exchange.

All markets (e.g. WETH/USDC) are traded through a single central exchange contract, and all order book and trading operations must be routed through it. The one exception is that each market itself is an ERC20 liquidity token, so its standard token functions can be called on the market contract directly. To get the address of each canonical market, query `getMarketByTokens` with the two token addresses on the exchange contract. The central exchange contract is the only relevant entrypoint to the core protocol and contains router endpoints designed for ease of use and lower level exchange endpoints designed for greater versatility. It is recommended that the router endpoints (e.g. `swapExactTokensForTokens`) on the exchange contract are used when directly interacting with Crystal, as endpoints such as `marketOrder` are intended for market makers and DeFi integrations.

In addition to the limit order book, each market is also a constant-product automated market maker (AMM). Liquidity providers can deposit both assets of a market to receive an ERC20 liquidity token representing their share, redeemable at any time. When a market order executes, Crystal automatically routes across both resting limit orders and the AMM curve interchangeably to give the taker the best available price. The AMM can be enabled or disabled per market, and the AMM has a fixed 50bps spread to which proceeds accrue to liquidity providers. Liquidity-related actions emit `Mint`, `Burn`, and `Sync` events.

The exchange contract supports the native token of the blockchain, such as MON for Monad, through the router endpoints. It also supports read functions to calculate input/output amount. The core write methods are designed to resemble the Uniswap V2 router for familiarity while also allowing for multihop swaps (e.g. WETH to USDC to WBTC), with the only difference being the addition of an optional referral address.

The router also provides an additional swap function to leverage the capabilities of Crystal's order book that most AMMs lack, which are:

* Orders that partially fill up to a set price.
* Market-to-limit orders that fill up to a set price and then set a limit order with the remaining unfilled portion.

The router also has functions for placing and canceling limit orders, with support for native tokens as well. Finally, the router also allows for order batching across multiple markets in a single transaction, claiming fees from all markets in a single transaction, and trades that automatically route across the launchpad and its graduated markets.

Crystal supports a single privileged party, which can be queried at any time with the `gov` function. Anyone can create new markets with parameters set to their desire, but the privileged party is able to elevate other addresses to become canonical deployers, allowing them to deploy canonical markets. This process is done to ensure end users only see markets configured with reasonable parameters, and over time will be decentralized to protocol governance. Additionally, select market parameters can be altered by this privileged party, but these changes cannot affect existing orders or move user funds. In a canonical market, the taker fee, maker rebate, and minimum order size can be adjusted by protocol governance, or by the market creator if the creator is also a canonical deployer. The market creator can additionally reassign the creator address that receives the creator fee split. Other parameters can only be changed by creating a new replacement market with the same tokens. In this case, the new market will overwrite the former pair in the router's token addresses to market address mapping. The old market will continue to function as normal, but it is recommended to use the latest created market as the router will automatically use the new market. Parameters will only be changed rarely by protocol governance and with reason. By default, interactions with the router will use the latest created market, so this is not an issue for end users to worry about. Tokens are only approved once to the exchange contract, individual markets do not need to be seperately approved. Importantly, neither a misconfigured market nor the privileged party can tamper with user funds either from existing orders or through approvals.

Each market is between a quote asset and base asset, and the central exchange contract contains read functions to query relevant parameters the current state of the order book, individual price levels and orders, as well as estimated input/output quotes for trades. There are also methods to batch these queries, designed for use by frontends and off-chain interfaces. Some of these values may be packed for efficiency, and further information can be found in the [Smart Contract Methods](/developer-resources/smart-contract-methods) section. Buy orders are defined as exchanging the quote asset for the base asset, while sell orders are defined as exchanging the base asset for the quote asset. Buy orders are denominated in terms of the quote asset, while sell orders are denominated in the base asset for both market and limit orders. If the output amount is specified rather than the input amount, the converse is true. Price is always defined as `price = (quote asset amount * scale factor / base asset amount)`, with scale factor being a parameter used to allow for decimal precision. For example, for a market WETH/USDC with a scale factor of 10<sup>13</sup> where WETH has 18 decimals and USDC has 6 decimals, a price of 4,123.4 USDC per WETH would be represented as 41234.

At the core of each market are the limit orders, which collectively make up the order book. Limit orders are constrained by:

* a minimum size
* a maximum price
* the bid-ask spread

The minimum size constraint is only applicable upon modification of the order by the owner, including new order placements and decreases. This means that a taker fill can reduce the size of an order to below the minimum resting size without canceling the remainder. The minimum size and maximum price constraints are designed to mitigate spam and lower gas for all users. To place a limit order with the potential of crossing the bid-ask spread, ensure that add liquidity only is disabled to place a market-to-limit order which will fill as a taker order up until your limit price if necessary.

Based on the market type, Crystal provides the option to use a base-10 fixed-point price grid rather than linear price ticks. With this setting enabled, prices support 5 significant digits of precision at all times, with the minimum permitted price increment changing by powers of 10 as the price increases. For example, a price below 0.1 may increment by 0.00001, while prices from 0.1 to 1 increment by 0.0001, from 1 to 10 by 0.001, and so on. This means that for market types of 2 and above, 1.0001 could be a valid price tick while 10.0001 would not. Unlike logarithmic tick systems, this architecture allows price levels to remain intuitive while also allowing for optimal price precision regardless of the asset price. Market types 0 and 1 use a standard linear tick system where price precision is preset and does not change across price ranges.

Within each price level, limit orders are stored in a doubly linked list, and orders are filled on a first-in first-out basis. Each order receives a unique ID and is mapped (ID -> linked list node). Placing an order simply appends it to the linked list, while cancelling removes the corresponding `ID` -> `node` pair from the hashmap and reconnects its neighboring nodes, thus providing `O(1)` operations for both placement and cancellation of orders. Price levels are stored as a sparse array from 0 up until the maximum price. A bitmap marking price levels with active orders is used to traverse across these price levels in near-constant compute. When the size of a limit order is either queried or emitted, the resulting value is denominated in the quote asset in the case of a buy order and base asset in the case of a sell order. Limit orders are either feeless or recieve a fee rebate when filled, importantly they never pay a fee and are guaranteed to execute at the specified price or better.

Limit orders can be canceled based on their price and unique id if they have not yet been filled. Placement and cancelation of limit orders are `O(1)` operations, and each order and price level is packed into a singular storage slot. For market makers, the calldata necessary to place or cancel an order is a single 32-byte chunk, and multiple of these operations can be batched into a single transaction. Both placement and cancelation of limit orders will emit events containing the order's owner, id, price, and size. Limit orders placed and canceled in batches will emit batched event logs as the indexed owner parameter can be reused, while the data of each individual order follows in 32-byte chunks. Individual fills are emitted seperately and can be subscribed to through an indexed owner parameter. Market makers also have the option and are recommended to specify a client order id for a variety of reasons including easier indexing, cheaper gas, and the ability to cancel inflight orders. In this case, the emitted id consists of the owner and client order id which is irrespective of market or price level. Importantly, client order ids must be unique if the previous order is still active, but can and should be reused once canceled or filled.

Market orders match the existing order book and are executed immediately. They fill orders up until their designated worst acceptable price, at which they will either terminate, revert, or transform into a limit order with the remaining portion depending on the order type. Either the desired input or output amount can be specified, with denominations in their respective tokens. Tokens are directly transferred from the address placing the market order to the owner of each limit order filled, therefore requiring token approval. The router handles this automatically when swapping with native tokens. Market orders emit an event containing the address, amount in, amount out, last filled price, and a list of all limit orders filled. A fixed fee is charged, with the potential for a referral commission and/or creator commission. This fee is modifiable on market deployment and can be altered by governance, but is unlikely to frequently change once a market is deployed. There is a maximum protocol fee of 10% to prevent malicious attacks in the unlikely event the governance address becomes compromised. Protocols integrating Crystal's liquidity are encouraged to apply their referral address in the calldata of their transaction.

All exchange functionality occurs fully on-chain, including the order book, matching engine, and necessary data for both market makers and end-user interfaces. Besides parameters necessary to ensure optimal performance, all deployed contracts are both permissionless and immutable. For additional questions, please reach out through our [Discord](https://discord.gg/CrystalExch).


# Trust Model

The different roles played by the various actors that use Crystal and the privileges they have.

Crystal's core contracts are permissionless, immutable, and non-custodial: privileged parties can tune parameters within hard-coded limits and curate which markets are shown to users, but cannot move user funds, alter existing orders, or change core market logic.

### Actors

Governance: the single privileged protocol administrator (the gov address; also the contract owner, since onlyOwner is defined as msg.sender == gov).\
Canonical Deployers: addresses elevated by Governance that are permitted to deploy canonical (vetted) markets.\
Market Creators: anyone; market creation is permissionless. Markets they create are non-canonical unless the creator is also a canonical deployer.\
Fee Recipient: the address that receives protocol fees, set by Governance.\
Referrers: addresses supplied in trade calldata that earn a share of the protocol fee on that trade.\
Market Makers: users who place and cancel resting limit orders.\
Traders: users who place market orders and router swaps.\
Liquidity Providers: users who deposit into a market's AMM and hold its ERC20 liquidity token.\
Routers / Integrators / Forwarders: contracts or frontends that route orders; a user may explicitly approve a forwarder to act on their behalf.\
Vault Governance: the administrator of the Vault Factory (gov on CrystalVaultFactory), which sets factory-wide bounds.\
Vault Owners: strategists who deploy and manage an individual vault.\
Vault Depositors: users who deposit into a vault in exchange for ERC20 vault shares.

### &#xD;Roles and Permissions

#### &#xD;Governance (gov)

Can: transfer governance (changeGov); set the protocol fee recipient (changeFeeRecipient); set the referral fee commission, capped at 50% of the protocol fee (changeRefFeeCommission); set the fee-claim window, minimum 1 day (changeFeeClaimDuration); add and remove canonical deployers; change any market's taker fee, maker rebate, minimum size, AMM-enabled flag, and canonical status (changeMarketParams); change any market's creator and creator fee split (changeMarketCreatorFee); update global launchpad parameters (changeLaunchpadParams); claim fees left unclaimed past the fee-claim window (executeClaimExpiredFees).\
Cannot: set any market fee above the 10% protocol maximum; modify, cancel, or seize existing orders; move, freeze, or withdraw user deposits, balances, or LP positions; change a market's immutable parameters (scale factor, tick size, max price, market type, token pair) — those require deploying a new market; set referral commission or creator fee split above 50% of the fee; upgrade, pause, or alter deployed contract logic (contracts are immutable).

#### Canonical Deployers

Can: deploy canonical markets with vetted parameters; change the parameters of markets they themselves created (requires being both a canonical deployer and that market's creator).\
Cannot: exceed the 10% fee cap; change markets they did not create (only Governance can); elevate other canonical deployers; touch user funds or existing orders.

#### Market Creators (anyone)

Can: permissionlessly deploy a market for any token pair with chosen parameters; reassign their market's creator address (which receives the creator fee split); earn the creator fee split on their market's taker fees.\
Cannot: mark a market canonical, or change its taker fee / maker rebate / minimum size, unless also a canonical deployer; exceed fee caps; affect other markets, existing orders, or user funds.

#### Fee Recipient, Referrers, and Creators (passive)

Can: accrue and claim their share of protocol, referral, or creator fees as claimable balances (claimFees). Referrers earn only when their address is included in a trade's calldata.\
Cannot: exercise any control over markets, orders, liquidity, or user funds — these roles are purely economic.

#### Market Makers, Traders, and Liquidity Providers (users)

Can: place and cancel their own limit orders (makers); place market orders and swaps (traders); add and remove AMM liquidity (LPs); grant a single token approval to the exchange covering all markets.\
Cannot: access another user's funds or orders; bypass a market's minimum size, maximum price, or tick-size constraints.

#### Forwarders / Integrators

Can: place or manage orders on behalf of a user who has explicitly approved them (approveForwarder).\
Cannot: act for users who have not approved them; the user can revoke at any time (removeForwarder).

#### Vault Governance (Vault Factory gov)

Can: transfer factory governance; set factory-wide bounds — maximum order cap, maximum lockup, global minimum deposit, and per-token minimum deposit.\
Cannot: access or move assets in any individual vault; override a vault owner's management of their vault; touch exchange-level markets or funds.

#### Vault Owners (strategists)

Can: deploy a vault and act as its strategist; manage strategy — select or refresh the market, set the order cap, lockup, max shares, and decrease-on-withdraw behavior; place the vault's orders; claim the vault's fees (paid to the owner); close the vault.\
Cannot: exceed factory-set bounds; reduce their own stake below the required minimum owner share (more than 5% of outstanding shares) except when closing the vault, which keeps the strategist aligned with depositors; seize depositor funds — withdrawals are pro-rata by share.

#### Vault Depositors

Can: deposit for ERC20 vault shares and withdraw pro-rata, subject to the vault's lockup.\
Cannot: direct the vault's strategy — that authority belongs to the vault owner.

### Operations

Privileged actions are ordinary on-chain function calls made by the relevant governance address, and the contracts themselves do not enforce a timelock on parameter changes — a change takes effect as soon as its transaction is included. Their impact is bounded instead by hard-coded, in-contract limits rather than by delay.

Market parameters (taker fee, maker rebate, minimum size, AMM toggle, canonical status) are changed via changeMarketParams, callable by Governance for any market, or by a market's creator if that creator is a canonical deployer. Creator/fee-split changes go through changeMarketCreatorFee. In-contract checks enforce the 10% fee ceiling, the ≤50% split cap, minimum-size validity, and tick alignment, so an out-of-bounds change reverts rather than applying.

Protocol-level settings (fee recipient, referral commission, fee-claim window, launchpad parameters, and the canonical-deployer set) are Governance-only.

Vault settings split cleanly: factory-wide bounds are set by Vault Governance, while per-vault settings are initiated by the vault owner and routed through the factory to the vault — so the factory is the single mediating entrypoint for vault administration.

Delay-gated operations do exist for a few non-parameter actions: closing an inactive market (queueCloseInactiveMarket → executeCloseInactiveMarket) and claiming expired fees (queueClaimExpiredFees → executeClaimExpiredFees) follow a queue-then-execute pattern governed by the fee-claim window.

Because the contracts are immutable and non-custodial, the blast radius of any governance action is limited by design: Governance can retune economics within fixed caps and curate canonical markets, but cannot reach existing orders, user balances, LP positions, or the core matching and pricing math.


# Audits

Crystal has concluded a security review with Sherlock and Zellic (Zenith). In our latest security review, no Critical or High vulnerabilities were found. The report is attached here:

{% file src="/files/MuXqX0fwSsWjUw1b1TJe" %}


# Bug Bounty

Crystal is protected by Sherlock Shield for up to $150,000 of bug bounty and exploit coverage.

Please report any smart contract errors to <security@crystal.exchange>, quality reports will be rewarded.


# Known Limitations

Below is a non-exhaustive list of the current limitations and design assumptions of the Crystal protocol.

Below is a non-exhaustive list of the current limitations and design assumptions of the Crystal protocol.

1. Non-standard tokens are not supported. Crystal assumes tokens are standard ERC20s that revert on failure and transfer exactly the requested amount.
2. Quotes are estimates, not guarantees; getQuote, getAmountsOut, and getAmountsIn are computed by simulating against price levels rather than individual orders. In addition, orderbook and AMM state at call time may differ between quote and trade execution. As a result, actual fills may differ from displayed preview amounts. Always pair any trade with a well-placed slippage bound using either a specified worst price or minimum output.
3. Execution is gas-bounded, which caps order size. The entire order must execute within Monad's 30M per-transaction gas limit. Orders that would exceed this must be split across transactions or placed as market-to-limit orders so the remainder rests instead of reverting.
4. Gas-aware orders may be underfilled. Gas-aware partial order types stop filling once fewer than \~200,000 gas remain in the transaction, even when more liquidity is available at an acceptable price. This bounds worst-case gas but means such orders may fill less than a quote suggests, and the exact stopping point depends on the transaction's gas budget, which displayed quotes may not perfectly model.
5. All prices must be valid tick sizes below the market's maximum price; prices between ticks cannot be expressed, so conversions between base and quote round can leave small amounts of dust.
6. Market orders with a worstPrice of 0 will execute without a slippage limit. Beware especially when placing market-to-limit orders as they will fill up to an uncapped price.
7. Some aggregate view functions cap their output; for example, getAllOrdersByCloid returns at most 1024 orders, so interfaces indexing large accounts must paginate calls. Upgradeability Crystal's contracts are immutable by design. As a result, upgrades are handled through new deployments and opt-in migration ("social migration"). Future features, optimizations, or disclosed vulnerabilities that require deployment will ship as a new set of contracts, while the existing deployment continues to operate unchanged. Since all state, such as resting limit orders and LP positions, exist in the current contracts, migration is voluntary. Users and market makers may withdraw or cancel orders on the old deployment and re-place orders or re-quote on the new deployment. Frontends and integrators may point their applications to the new deployment addresses, so end users are automatically routed to the latest version automatically. Assumptions Crystal's design assumes the following about the tokens it lists and the network it runs on. Listed tokens are assumed to be standard, well-behaved ERC20s that revert on failure and transfer exactly the requested amount. Tokens that deviate from these expectations are not supported. Crystal is tuned for Monad, which differs from standard EVM chains in ways that directly affect order execution: • The block gas limit is 200M gas, the per-transaction gas limit is 30M gas, and block time is \~400ms; since the entire orderbook is on-chain, these limits bound what a single transaction can do. • A market order's gas cost scales with the number of resting orders and price levels it crosses, measured at roughly 65,000 gas of fixed overhead plus \~14,500 gas per price level (tick) filled. The whole order must execute within the 30M per-transaction limit. For example, a market order crossing 100 ticks costs \~1.5M gas on Monad (about 0.76% of a 200M-gas block) and a single order can cross on the order of \~2,000 ticks before reaching the per-transaction limit. Orders that would sweep more than that must be split across transactions or placed as market-to-limit orders so the unfilled remainder rests instead of reverting. • Crystal assumes Monad's raised contract-size limit (128 KB), well above the standard 24 KB EIP-170 cap. The core contracts exceed 24 KB (the per-market contract alone is \~55 KB), and Crystal is split into a central exchange contract, per-market contracts, and libraries partly to stay within the limit. It will not deploy on EVM chains that enforce the 24 KB cap. These figures reflect Monad's current mainnet configuration and may change as the network adjusts its limits; order-sizing assumptions should be revisited if the per-transaction gas limit changes.


# Risks

Robust risk management measures are in place, but regardless users should remain informed of risks. Below is a non-exhaustive list of dangers the Crystal protocol could potentially be exposed to.

### Smart Contract Risk

Crystal's order matching and settlement executes on-chain in immutable smart contracts. As with any smart contracts, may contain undiscovered bugs or vulnerabilities which could result in loss of funds. To mitigate these risks, Crystal’s code has undergone external third-party professional audits, and the protocol runs a bug bounty program and an extensive testing framework.

### &#xD;Slippage Risk

Trades execute against live liquidity, and prices may change between the time a transaction is submitted and when it is executed. To protect against this, every trade enforces a slippage bound and deadline, and reverts if those limits would be exceeded. Setting loose slippage bounds, particularly in thin or volatile markets, can result in significantly worse trading rates.

### &#xD;Partial Fill Risk

An order may only be partially filled before reaching its worst acceptable price. Depending on order type, the unfilled remainder will be returned, be converted into a resting limit order (a market-to-limit order), or the transaction may be reverted.

### &#xD;Market Configuration Risk

Anyone can permissionlessly create a market with arbitrary parameters of their choice, so a given token pair may have multiple markets, potentially with unreasonable parameters. Canonical markets, in contrast, are configured with vetted parameters, and the router automatically routes to the latest canonical market for a pair, so end users are shown reasonable defaults. Interacting directly with a non-canonical or misconfigured market is at the user's own risk.


# Glossary

Refine your knowledge of crypto-related vocabulary and learn the technical terms used in Crystal's documentation.

## Generic Terms:

* **CLOB (Central Limit Order Book):** An exchange architecture that matches user trades with an existing book of user-placed limit orders.
* **AMM (Automatic Market Maker):** A decentralized exchange model where swap quotes are algorithmically determined based on the reserves in a user-supplied liquidity pool.
* **Maker order:** An order that adds liquidity to the order book, such as a limit order, is called a maker order.
* **Taker order:** An order that removes liquidity from the order book, such as a market order, is called a taker order.
* **Blockchain:** A shared, immutable ledger that facilitates transactions across a network.
* **Smart Contract:** A computer program located on the blockchain that executes deterministically when transacted with.
* **On-chain:** Computation or activity that takes place directly on the blockchain.
* **Off-chain:** Computation or activity that occurs outside of the blockchain.
* **DeFi (Decentralized Finance):** An umbrella term for peer-to-peer financial applications utilizing blockchain technology.
* **CEX (Centralized Exchange):** An exchange managed by a central authority operating on off-chain servers.
* **DEX (Decentralized Exchange):** An exchange operated by smart contracts located on the blockchain rather than off-chain servers.
* **EVM (Ethereum Virtual Machine):** The computation engine that executes smart contracts on networks such as Ethereum and Monad.
* **Gas:** Comparable to electricity or energy in the real world, gas is required to perform any action on the blockchain. Gas fees fluctuate based on the complexity of the action and the network's congestion level.
* **Wrapped Monad:** An ERC-20 wrapper of Monad that allows it to be more easily integrated in DeFi.
* **Bonding Curve:** An AMM featuring virtual reserves to facilitate early stage trading while protecting against snipers. Tokens created by launchpads tend to start on the bonding curve before eventually migrating to a DEX where the virtual reserves are removed.

## Crystal Technical Terms:

* **Scale Factor:** The power of ten quote asset amounts are multiplied by to allow for trading at decimal prices. For example, a scale factor of 13 would multiply the quote asset amount by 10\*\*13 for the purpose of calculation only.
* **Price Factor:** How many decimals of precision a market supports accounting for the difference between decimal counts of the two tokens.
* **Bid-Ask Spread:** The area consisting of price levels above the highest buy order but below the lowest sell order.


# Market Making

Anyone is welcome to provide liquidity on a level playing field. There are no advantages given to any specific parties. Market trades, limit order placement, and limit order cancelation all emit events containing the price, size, maker, and order id if applicable. Individual limit order fills do not emit an event, the market trade which fills the order emits one instead. The [fallback](broken://pages/b27c3b6047b99eac29627a601a7efdd8a7d359d2#fallback) function is designed as a gas-optimized endpoint for market makers, featuring order batching with reduced calldata size. In order to utilize this, the registerUser function must be first called once on every unique address, note that this is not necessary for regular traders. Market orders placed using the fallback function automatically self-refer to receive the maximum fee discount and do not disqualify the user from any incentive programs. The creation of a referral code is not necessary to take advantage of this feature.

### Calculating Limit Price

The limit price is equal to the quote asset in its smallest denomination multiplied by the scale factor divided by the base asset amount in its smallest denomination. For example, with an ETH/USDC market with a scale factor of 10<sup>13</sup>, the price of 3,001.1 USDC per ETH would be represented as 30011, as ETH has 18 decimals and USDC has 6, so `(3,001.1 * 10 ^ 6) * 10 ^ 13 / (1 * 10 ^ 18) = 30011`.

For markets of any marketType other than 0, precision is fixed at up to five significant figures. For example, 3,293.1 is a valid price and so is 0.00012342, but 111,23<mark style="color:$danger;">3</mark> is invalid as is 0.014928<mark style="color:$danger;">3</mark>. Scaling is the same as with other markets, so on a MON/USDC market with scale factor 10<sup>21</sup>, the price 3.2871 USDC per MON is represented as 32871<mark style="color:$warning;">00000</mark>. Note that in this case, a value like 32871<mark style="color:$danger;">1</mark><mark style="color:$warning;">0000</mark> would be an invalid price point as it has six significant figures.

### Gas Optimization

Unlike many centralized alternatives, Crystal is fully onchain, and that means that market makers must pay gas to place and cancel orders. Transactions on Monad are extremely cheap, and gas should not be too much of an issue. That said, there are several things that can be done to quote more efficiently:

* Use client order id orders: In addition to the ability to cancel in-flight orders, attaching a client order id allows your order to reuse a storage slot, saving gas
* Use internal balances: Using internal balances saves gas as tokens don't have to be seperately sent on every order placement/cancellation
* Use an access list: Due to Monad's parallel architecture, the cost of cold storage access is increased. By using an access list with your transaction, the cost can be significantly reduced. It is recommended to include the storage slot of the market, storage slot of your order, storage slot of the price level, and storage slot of your balances in your access list when placing/canceling an order
* Batch orders across markets: You can batch multiple actions in a single call to the fallback endpoint. You can also batch interactions with multiple markets in a single transaction. This saves significant amounts of gas as much of the storage across markets is shared. You can also specify whether to revert or continue on each action failure for the active batch. Multiple batches can be used in a single transaction to a single market as well
* MEV bid endpoint: By adding a msg.value, you can specify a MEV bid for validators to prioritze your transactions. These bids will only be paid if the function call succeeds. Multiple function calls can be made in a single transaction, each with their own individual bid to be paid out conditionally. Note that due to Monad's asynchronous architecture, validators may not have the ability to simulate all transactions and therefore bids do not guarantee priority inclusion

An example market maker implementation that is not intended for production use can be found [here](https://github.com/CrystalExch/Example-MM). Source code for the Crystal protocol is available [here](https://github.com/CrystalExch/Crystal-Contracts). For any questions or clarifications, feel free to contact our team through our [Discord](https://discord.gg/CrystalExch).


# Aggregator / DeFi Integration

Crystal is a fully on-chain orderbook exchange. As a result, the output of a swap cannot determined by any algorithm such as `x * y = k`, and quotes must either be simulated onchain or locally. The L2 book can be directly queried onchain with a single rpc [method](broken://pages/b27c3b6047b99eac29627a601a7efdd8a7d359d2#getpricelevelsfrommid), while the L3 book is also fully onchain but must be queried [manually](broken://pages/b27c3b6047b99eac29627a601a7efdd8a7d359d2#getorder). Every order placement, cancelation, and fill emit events that carry sufficient information to update a local copy of the L3 book.

A couple of variables are relevant in determining the behavior of a market:

* quoteAsset: If this is the input asset, the action is a buy order.
* baseAsset: If this is the input asset, the action is a sell order.
* marketType: If the market type is 0, valid price ticks are defined as multiples of tickSize. Otherwise, price ticks are valid if they have five significant figures and are a multiple of tickSize.
* tickSize: Price ticks must be a multiple of tickSize to be valid. In most markets, this will be set to 1.
* maxPrice: The maximum (exclusive) price bound for a limit order.
* minSize: The minimum valid size of a limit order, denominated in the quote asset.
* takerFee: The taker fee paid by all market orders to the protocol. (100,000 - takerFee) / 10 is equivalent to the taker fee in basis points.
* makerRebate: The maker rebate paid to the owner of a resting limit order. This fee is only paid on quantities filled against the orderbook. The total effective fee on a market order is takerFee + makerRebate. (100,000 - makerRebate) / 10 is equivalent to the taker fee in basis points.
* reserveQuote: Quote asset reserve for the AMM.
* reserveBase: Base asset reserve for the AMM.

It is recommended to retain a local copy of the L2 book in order to simulate quotes locally. To do this, the current state of the orderbook can be snapshotted with [getPriceLevelsFromMid](broken://pages/b27c3b6047b99eac29627a601a7efdd8a7d359d2#getpricelevelsfrommid), and updates can be subscribed to via the [ordersUpdated](broken://pages/b27c3b6047b99eac29627a601a7efdd8a7d359d2#ordersupdated) and [Fill](broken://pages/b27c3b6047b99eac29627a601a7efdd8a7d359d2#fill) events. Alternatively, a polling-based approach can be used, where repeated snapshots could be taken every couple of seconds. Resting quantities are in denominated in their underlying asset, so in an ETH/USDC market where USDC is the quote asset, buy orders are represented in their USDC amount while sell orders are represented in their ETH amount.

Some pairs also have a backstop constant product AMM. To check whether a market has the AMM enabled, getMarket can be called or MarketCreated and MarketUpdated events can be subscribed to. The AMM reserves can be queried with getReserves, and a Sync event is emitted in place of a fill event when reserves are altered. Trades are routed to the best source of liquidity, following a procedure best simplified as:

1. Check the price of the next limit order
2. Check the AMM price
3. If the AMM price is more competitive, fill the AMM until either the order quantity is exhausted or the marginal execution price is equal to the limit order price adjusted for maker rebates, whichever comes first
4. If there is any remaining amount, fill the limit order
5. Repeat if needed until the order quantity is exhausted or the slippage limit of the trade is hit

Majors and correlated pairs will have the AMM disabled, while markets graduated from our launchpad will primarily rely on the AMM liquidity until sufficient trading interest is reached. That said, a resting limit order could potentially give users a much better fill, so it is recommended to index/simulate the orderbook as well. If you would like to see the exact implementation of the AMM, please reach out to our team.&#x20;

Alternatively, the [getAmountsOut](broken://pages/b27c3b6047b99eac29627a601a7efdd8a7d359d2#getamountsout) and [getAmountsIn](broken://pages/b27c3b6047b99eac29627a601a7efdd8a7d359d2#getamountsin) function on the exchange contract can be called to easily simulate quotes, but this is not recommended for aggregators. To directly simulate a quote on an individual market, the [getQuote](broken://pages/b27c3b6047b99eac29627a601a7efdd8a7d359d2#getquote) method can also be called with the option to set a slippage limit, or price point at which the quote terminates and the filled portion is returned.

To conduct a trade, it is recommended to directly call [marketOrder](broken://pages/b27c3b6047b99eac29627a601a7efdd8a7d359d2#marketorder) on the exchange contract with the specified market. To participate in our fee sharing [program](broken://pages/QHnmtTu6EIMPi3RFna3I), pass an address controlled by the team operating the integration as the referrer parameter in order to recieve a fee commission. Any referral earnings can be [claimed](broken://pages/BbzA31Rue64mtlgHhdtL#claimfees) onchain anytime through the Crystal [interface](https://app.crystal.exchange/referrals), and no creation of a referral link is necessary.

Source code for the Crystal protocol is available [here](https://github.com/CrystalExch/Crystal-Contracts). If you have any questions or would like to notify our team that you plan on integrating Crystal, please contact our team through our [Discord](https://discord.gg/CrystalExch).


# Launchpad Integration

This page describes how applications should integrate with Crystal Launchpad.

For a high-level explanation of how the launchpad works, see the Launchpad Overview.

### Integration Model

Launchpad tokens have two states:

| State       | Trading source          | Recommended functions                            |
| ----------- | ----------------------- | ------------------------------------------------ |
| Ungraduated | Virtual bonding curve   | `quoteBuy`, `quoteSell`, `buy`, `sell`           |
| Graduated   | Crystal orderbook + AMM | Standard Crystal swaps/orders, or `buy` / `sell` |

A Crystal market is created when the token is created, but it is not routable through the normal pair router until graduation.

Before graduation:

```
getMarketByTokens(weth, token) = placeholder
```

After graduation:

```
getMarketByTokens(weth, token) = real market
```

Because the placeholder is nonzero, integrations should not use `market != address(0)` alone to determine whether the token has graduated.

### Detecting Launchpad State

#### `getVirtualReserves`

```solidity
function getVirtualReserves(address token)
    external
    view
    returns (
        uint256 virtualNativeReserve,
        uint256 virtualTokenReserve
    )
```

For an active launchpad token, both values are nonzero.

After graduation, the launchpad state is deleted and both values return zero.

This is the simplest way to distinguish the active bonding-curve state.

#### `launchpadTokenToMarket`

The public mapping exposes:

```
virtualNativeReserve
virtualTokenReserve
k
creator
market
```

The `market` field is the future Crystal market address, even before that market becomes routable.

After graduation, the mapping entry is cleared.

#### `wasLaunchpad`

```solidity
function wasLaunchpad(address market) external view returns (bool)
```

Returns true if a market originated from the launchpad.

#### `allTokens`

```solidity
function allTokens(uint256 index) external view returns (address)
```

Enumerates launchpad-created tokens.

### Creating a Token

```solidity
function createToken(
    string name,
    string symbol,
    string metadataCID,
    string description,
    string social1,
    string social2,
    string social3,
    string social4
) external payable returns (address token)
```

The `name` and `symbol` must be nonempty.

Creation:

* deploys a new `CrystalToken`;
* creates its future type-4 Crystal market;
* stores the token in `allTokens`;
* initializes the bonding curve;
* prepares the graduated AMM position; and
* emits `TokenCreated`.

If `msg.value > 0`, Crystal immediately performs an exact-input buy using that native amount.

### Buying

```solidity
function buy(
    bool isExactInput,
    address token,
    uint256 amountIn,
    uint256 amountOut
) external payable returns (
    uint256 inputAmount,
    uint256 outputAmount,
    bool isLaunchpadClosed
)
```

Before graduation, the trade executes against the virtual curve.

After graduation, the same function routes through the normal Crystal market.

#### Exact-input mode

```
isExactInput = true
```

Then:

```
amountIn  = exact native input
amountOut = minimum acceptable token output
```

`msg.value` must equal `amountIn`.

#### Exact-output mode

```
isExactInput = false
```

Then:

```
amountOut = exact token output requested
amountIn  = maximum acceptable native input
```

If `amountIn == 0`, the final maximum-input check is disabled.

The transaction must still provide enough native value for the trade.

#### Graduation during a buy

A single buy can reach graduation.

Crystal first executes the amount required to finish the bonding curve. It then activates the graduated market and can continue any remaining requested trade through the newly active market.

Applications should therefore treat graduation as something that can occur **inside a trade**, rather than as a separate state transition transaction.

### Selling

```solidity
function sell(
    bool isExactInput,
    address token,
    uint256 amountIn,
    uint256 amountOut
) external returns (
    uint256 inputAmount,
    uint256 outputAmount
)
```

Before graduation, the trade executes against the virtual curve.

After graduation, it routes through the normal Crystal market.

#### Exact-input mode

```
amountIn  = exact number of tokens to sell
amountOut = minimum native output
```

#### Exact-output mode

```
amountOut = exact native output requested
amountIn  = maximum token input
```

If `amountIn == 0`, the maximum-input check is disabled.

The user must approve Crystal to transfer the launchpad token.

### Quoting

#### `quoteBuy`

```solidity
function quoteBuy(
    bool isExactInput,
    address token,
    uint256 amountIn,
    uint256 amountOut
) external returns (
    uint256 inputAmount,
    uint256 outputAmount,
    bool graduated
)
```

Uses the same exact-input/exact-output semantics as `buy`.

The `graduated` return value is true when the quoted trade would graduate the token or when the quote uses the graduated market.

A quote that reaches graduation accounts for the launchpad portion and the immediate post-graduation market transition.

#### `quoteSell`

```solidity
function quoteSell(
    bool isExactInput,
    address token,
    uint256 amountIn,
    uint256 amountOut
) external returns (
    uint256 inputAmount,
    uint256 outputAmount
)
```

Uses the same semantics as `sell`.

Before graduation it quotes the bonding curve. After graduation it quotes the Crystal market.

#### Quote functions are read-like

`quoteBuy` and `quoteSell` do not persist reserve changes, but they are not declared Solidity `view`.

Integrations should call them with `eth_call` / simulation rather than sending a transaction.

### Curve State

An active launchpad stores:

```
virtualNativeReserve
virtualTokenReserve
k
```

where:

```
k = virtualNativeReserve * virtualTokenReserve
```

The initial values are:

```
virtualNativeReserve =
    launchpadInitialNativeSupply

virtualTokenReserve =
    INITIAL_CURVE_TOKEN_SUPPLY
```

Crystal uses integer rounding when updating reserves, so integrations should prefer `quoteBuy` and `quoteSell` over independently reproducing execution math.

### Supply Model

Launchpad tokens use 18 decimals.

Current constants are:

```
Total token supply:
1,000,000,000 tokens

Tokens moved through the curve before graduation:
800,000,000 tokens

Tokens reserved for the graduated AMM:
200,000,000 tokens
```

Crystal also adds virtual token reserves for pricing.

The active curve begins at approximately:

```
1,066,666,666.6667 virtual tokens
```

and graduates at approximately:

```
266,666,666.6667 virtual tokens
```

The difference is 800 million tokens.

The virtual amount is part of curve accounting and should not be interpreted as additional circulating supply.

### Graduation Condition

Graduation occurs when:

```
virtualNativeReserve >=
    ceil(k / GRADUATED_CURVE_TOKEN_SUPPLY)
```

Once this condition is reached Crystal:

* clears `launchpadTokenToMarket[token]`;
* sets `wasLaunchpad[market] = true`;
* replaces the placeholder pair mapping with the real market;
* applies graduated market parameters; and
* emits market initialization events.

Afterward, standard Crystal routing can use the pair.

### Graduated Market Parameters

Launchpad markets are created as type `4`, meaning the AMM fee is set at 1%, enabled by default, and the price ticks are base-10 with a fixed 5 digits of precision.

Their fixed core settings are:

| Parameter        |           Value |
| ---------------- | --------------: |
| Quote asset      |            WETH |
| Base asset       | Launchpad token |
| `marketType`     |             `4` |
| `scaleFactor`    |             `9` |
| `tickSize`       |             `1` |
| `maxPrice`       |          `1e15` |
| AMM enabled      |          `true` |
| AMM curve factor |  `9900 / 10000` |

Graduated fee and order-size parameters come from `launchpadParams`:

```
graduatedMinSize
graduatedTakerFee
graduatedMakerRebate
graduatedCreatorFeeSplit
```

### Launchpad Fees

`launchpadFee` is a multiplier scaled by `100000`.

For example:

```
launchpadFee = 99700
```

means:

```
99.7% remains after fee
0.3% is collected as fee
```

For buys:

```
amountAfterFee =
    floor(amountIn * launchpadFee / 100000)
```

For sells, the same factor is applied to native output.

Collected launchpad fees are split between:

```
token creator
protocol governance
```

according to:

```
launchpadCreatorFeeSplit
```

The resulting balances accrue in `claimableRewards`.

### Graduated AMM Liquidity

Crystal prepares the initial graduated AMM position when the token is created.

The base side is:

```
200,000,000 launchpad tokens
```

The quote side is derived from the curve's launch-to-graduation reserve relationship.

The LP tokens for this initial position are transferred to:

```
address(0)
```

so the initial launchpad-created LP position cannot be withdrawn by an ordinary LP holder.

### Events

#### Before graduation

A bonding-curve trade emits:

```solidity
event LaunchpadTrade(
    address indexed token,
    address indexed user,
    bool isBuy,
    uint256 amountIn,
    uint256 amountOut,
    uint256 virtualNativeReserve,
    uint256 virtualTokenReserve
);
```

The reserve fields are the virtual reserves **after** the bonding-curve portion of the trade.

#### Token creation

```solidity
event TokenCreated(
    address indexed token,
    address indexed creator,
    string name,
    string symbol,
    string metadataCID,
    string description,
    string social1,
    string social2,
    string social3,
    string social4
);
```

#### Graduation

Graduation emits:

```
MarketCreated
Sync
Mint
```

#### After graduation

Market execution uses normal Crystal events such as:

```
Trade
Fill
OrdersUpdated
Sync
```

A single transaction that crosses graduation can contain both launchpad and normal market events.

### Recommended Integration Flow

For a token page:

```
1. Query getVirtualReserves(token)
2. If reserves are nonzero:
      token is on launchpad
      use quoteBuy / quoteSell
      use buy / sell
3. If reserves are zero:
      query getMarketByTokens(weth, token)
      token is graduated
      normal Crystal routing is available
```

For trades that may graduate:

```
quoteBuy(...)
    ↓
display input/output
    ↓
buy(...)
    ↓
watch for LaunchpadTrade
    ↓
if graduation occurs:
    also process MarketCreated / Sync / Mint
    and any normal Trade / Fill events
```

### Important Integration Notes

* Do not treat a nonzero `getMarketByTokens` result as proof of graduation because the launchpad uses a nonzero placeholder before graduation.
* Prefer `quoteBuy` and `quoteSell` over reproducing the bonding-curve math locally.
* `buy` can cross graduation and continue into the graduated market in one transaction.
* `buy` and `sell` continue working after graduation.
* Before graduation, listen to `LaunchpadTrade`.
* After graduation, use the normal Crystal market events.
* A launchpad fee such as `99700` means a `0.3%` fee, not a `99.7%` fee.
* Launchpad tokens use 18 decimals.
* The 200 million-token graduated AMM position is separate from the 800 million-token launchpad distribution.


# Mainnet Changes

For aggregators/protocols who had our February 2025-August 2025 previous testnet deployment integrated.

#### Changes:

Individual markets are no longer in individual contracts. For example, instead of calling marketOrder(...) on the market contract like previously, you should now call marketOrder(marketAddress, ...) on the exchange contract. This also means you only have to approve the exchange contract instead of each individual market.

The event signatures have slightly changed. Notably, fill events are now emitted individually, allowing market makers to subscribe to their own fills only. Fill events also now carry both the remaining quantity and fill size as opposed to just the remaining quantity, allowing a L2 book to be maintained by subscribing to order updates and fills only.

There is now the addition of a decrease action, which decreases an order's size but does not change its priority. The size of the decrease is emitted rather than the remaining size, allowing a L2 book to be tracked without knowing of individual order sizes.

On an order fill, both the size of fill and remaining amount are emitted. This allows a L2 book to be maintained while tracking only placement, fill, decrease, and cancellation events.

#### New Additions:

Each market now has an optional parameter to enable constant product AMM backstop liquidity. Trades are automatically routed/split between the orderbook and AMM to give users the best quote. The AMM likely won't be enabled on major/stable markets, but will function as the primary source of liquidity for newly graduated markets. To preview execution through these markets, either call the quote simulation endpoints or query reserves and aggregate the two liquidity sources locally. The methodology used by Crystal can be found in our [source code](https://github.com/CrystalExch/Crystal-Contracts).

Crystal now has its own natively integrated launchpad. Tokens start on the bonding curve and migrate to the orderbook once graduated. The tokens collected in the bonding curve are deposited as AMM liquidity to begin with, but market makers can step in and place limit orders anytime post-graduation.

Maker rebates are now supported. The protocol taker fee is applied to matches against both the AMM and orderbook, while the maker rebate is only applicable when matching with the orderbook. In this case, the total fee on market orders is takerFee + makerRebate.


# Smart Contract Methods

Includes all function, structure, and event layouts.

<table data-view="cards"><thead><tr><th></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td>Crystal</td><td><a href="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2FHqgzjNvWjNvh476KVeB2%2FBanner.png?alt=media&amp;token=eecea7fe-0338-4eaf-ad5c-cfae7fb66cd9">Banner.png</a></td><td><a href="broken://pages/b27c3b6047b99eac29627a601a7efdd8a7d359d2">Broken link</a></td></tr><tr><td>CrystalVaultFactory</td><td><a href="https://2153026777-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0HP1ZdI86Q02qXLWK2XH%2Fuploads%2FHqgzjNvWjNvh476KVeB2%2FBanner.png?alt=media&amp;token=eecea7fe-0338-4eaf-ad5c-cfae7fb66cd9">Banner.png</a></td><td><a href="broken://pages/cdc3a5ca92613830127f8c8dbbc7feddad605377">Broken link</a></td></tr></tbody></table>


# Crystal

Crystal is the router and registry for all markets. Every trade, deposit, and market lookup goes through this contract; market contracts are called by delegatecall and never directly.

## Conventions

**Native token.** Wherever an asset address is accepted, `eth` (`0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`) means native ETH. Routing methods normalize it to `weth` internally. Deposited native balances are stored as WETH.

**Path.** An ordered asset route: `path[0]` is the input asset, `path[path.length - 1]` is the output asset. Every adjacent pair must have a canonical market registered in `getMarketByTokens`.

**Prices and fees.** Prices are integers in market tick units (see `getMarket` for `tickSize` and `scaleFactor`). Fees are scaled by 1e5, so `takerFee = 99700` means 0.3%. Total orderbook taker fee is `takerFee + makerRebate`; AMM fills do not apply `makerRebate`.

**Size.** For limit orders, `size` is denominated in quote for bids and base for asks. For market orders, `isExactInput` decides whether `size` is the input amount or the output amount.

**`worstPrice`** is inclusive: fills at exactly `worstPrice` are allowed.

**`user` parameter.** `address(0)` defaults to `msg.sender`. A nonzero address is accepted only if it equals `msg.sender` or has approved `msg.sender` via `approveForwarder`.

**`referrer` parameter.** Optional address that receives `feeCommission` percent of the taker fee. Pass `address(0)` for none.

**`deadline` parameter.** Unix timestamp. The call reverts with `Expired` if `block.timestamp` is past it.

**Order types** (`orderType` on `marketOrder` and `swap`):

| Value | Behavior                                                                                                                                |
| ----- | --------------------------------------------------------------------------------------------------------------------------------------- |
| 0     | Partial fill. Fills what it can within `worstPrice` and returns the remainder.                                                          |
| 1     | Complete fill. Reverts if the full size cannot be filled within `worstPrice`.                                                           |
| 2     | Market-to-limit. Fills what it can, then rests the remainder as a limit order at `worstPrice`. The returned `id` identifies that order. |
| 3     | Partial fill, gas-bounded. Same as 0 but stops matching when fewer than 200,000 gas remain.                                             |

**Self-trade prevention** (STP, in `marketOrder` options):

| Value | Behavior                                         |
| ----- | ------------------------------------------------ |
| 0     | Fill against own resting orders normally.        |
| 1     | Cancel the resting order and continue the taker. |
| 2     | Cancel the taker (remaining size is not filled). |
| 3     | Cancel both.                                     |

Batch and fallback market orders always use mode 1.

## Execution Options

Advanced methods take a packed `uint256 options`. Bit positions differ per method. All unlisted bits must be zero.

Balance routing has three sources/destinations: your wallet (default), the router's shared slot 0 (see Advanced Routing), and your deposited balance (see `deposit`). `useInternal` takes precedence over the router-slot flags.

**marketOrder**

| Bits  | Field        | Meaning                                                                         |
| ----- | ------------ | ------------------------------------------------------------------------------- |
| 0–40  | userId       | Optional. If nonzero, must be the id of `user`. Saves a lookup.                 |
| 44–53 | cloid        | Optional client order id (1–1023). Used for the fallback limit order in type 2. |
| 56–59 | stp          | Self-trade prevention mode (0–3).                                               |
| 60    | toInternal   | Credit output to router slot 0 instead of transferring to caller.               |
| 64    | fromInternal | Pull input from router slot 0 instead of `transferFrom` caller.                 |
| 68    | useInternal  | Debit and credit the user's deposited balance.                                  |

**limitOrder**

| Bits  | Field        | Meaning                                            |
| ----- | ------------ | -------------------------------------------------- |
| 0–40  | userId       | Optional.                                          |
| 44–53 | cloid        | Optional client order id (1–1023).                 |
| 56    | fromInternal | Pull collateral from router slot 0.                |
| 60    | useInternal  | Lock collateral from the user's deposited balance. |

**cancelOrder**

| Bits | Field       | Meaning                                                     |
| ---- | ----------- | ----------------------------------------------------------- |
| 0–40 | userId      | Optional.                                                   |
| 44   | toInternal  | Credit released collateral to router slot 0.                |
| 48   | useInternal | Credit released collateral to the user's deposited balance. |

**replaceOrder**

| Bits | Field        | Meaning                                                                                                                       |
| ---- | ------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| 0–40 | userId       | Optional.                                                                                                                     |
| 44   | allowTaker   | 0 = replacement is post-only. 1 = replacement is placed as a market-to-limit order at `newPrice` and may execute immediately. |
| 48   | isDecrease   | Treat the call as a size decrease at the same price.                                                                          |
| 52   | toInternal   | Credit outputs to router slot 0.                                                                                              |
| 56   | fromInternal | Pull additional collateral from router slot 0.                                                                                |
| 60   | useInternal  | Use the user's deposited balance.                                                                                             |

**batchOrders / Batch.options**

| Bits | Field        | Meaning                                      |
| ---- | ------------ | -------------------------------------------- |
| 0–40 | userId       | Optional.                                    |
| 44   | toInternal   | Credit net outputs to router slot 0.         |
| 48   | fromInternal | Pull net inputs from router slot 0.          |
| 52   | useInternal  | Settle against the user's deposited balance. |

## Batch Actions

`batchOrders` and `multiBatchOrders` take `Action` structs:

```solidity
struct Action {
    bool isRequireSuccess;
    uint256 action;
    uint256 param1;
    uint256 param2;
    uint256 param3;
}
```

If `isRequireSuccess` is true and the action does nothing (order not found, size below minimum, etc.), the whole batch reverts. Invalid parameters always revert regardless.

| Code | Action                         | param1                          | param2             | param3                                                         |
| ---- | ------------------------------ | ------------------------------- | ------------------ | -------------------------------------------------------------- |
| 1    | Cancel                         | price                           | native id          | cloid (if nonzero, cancels by cloid and ignores param1/param2) |
| 2    | Limit buy                      | price                           | size (quote)       | cloid, optional                                                |
| 3    | Limit sell                     | price                           | size (base)        | cloid, optional                                                |
| 4    | Market-to-limit buy            | worstPrice                      | size (exact input) | cloid for the rested remainder, optional                       |
| 5    | Market-to-limit sell           | worstPrice                      | size (exact input) | cloid, optional                                                |
| 6    | Partial-fill buy               | worstPrice                      | size (exact input) | —                                                              |
| 7    | Partial-fill sell              | worstPrice                      | size (exact input) | —                                                              |
| 8    | Partial-fill buy, gas-bounded  | worstPrice                      | size (exact input) | —                                                              |
| 9    | Partial-fill sell, gas-bounded | worstPrice                      | size (exact input) | —                                                              |
| 10   | Complete-fill buy              | worstPrice                      | size (exact input) | —                                                              |
| 11   | Complete-fill sell             | worstPrice                      | size (exact input) | —                                                              |
| 12   | Decrease                       | price (0 to reference by cloid) | amount to reduce   | native id (when price is nonzero) or cloid (when price is 0)   |

Market orders in a batch are always exact-input. All debits and credits are netted and settled once at the end of the batch.

## Market Data

#### allMarketsLength

```solidity
function allMarketsLength() external view returns (uint256)
```

Returns the number of deployed markets, including launchpad markets that have not graduated. Use with `allMarkets(uint256)` to enumerate.

**Return Values**

| Name | Type    | Description             |
| ---- | ------- | ----------------------- |
| —    | uint256 | Length of `allMarkets`. |

#### getMarket

```solidity
function getMarket(address market) external view returns (MarketInfo info)
```

Returns the persistent configuration and current state of a market. This is the canonical market-configuration source.

**Parameters**

| Name   | Type    | Description     |
| ------ | ------- | --------------- |
| market | address | Market address. |

**Return Values**

| Name         | Type    | Description                                              |
| ------------ | ------- | -------------------------------------------------------- |
| quoteAsset   | address | Quote asset.                                             |
| baseAsset    | address | Base asset.                                              |
| marketType   | uint256 | Market type (0–4). Types 2–4 have an AMM.                |
| highestBid   | uint256 | Best resting bid.                                        |
| lowestAsk    | uint256 | Best resting ask.                                        |
| scaleFactor  | uint256 | Price scale factor.                                      |
| tickSize     | uint256 | Minimum price increment.                                 |
| maxPrice     | uint256 | Maximum representable price.                             |
| minSize      | uint256 | Minimum resting limit order size, enforced at placement. |
| takerFee     | uint256 | Taker fee, scaled by 1e5.                                |
| makerRebate  | uint256 | Maker rebate, scaled by 1e5.                             |
| reserveQuote | uint256 | AMM quote reserve.                                       |
| reserveBase  | uint256 | AMM base reserve.                                        |
| isAMMEnabled | bool    | Whether the AMM is active.                               |

#### getPrice

```solidity
function getPrice(address market) external returns (uint256 price, uint256 highestBid, uint256 lowestAsk)
```

Returns the midpoint price and best bid/ask including AMM liquidity. Not a `view` because it delegates to the market; call it with `eth_call`.

**Parameters**

| Name   | Type    | Description     |
| ------ | ------- | --------------- |
| market | address | Market address. |

**Return Values**

| Name       | Type    | Description                        |
| ---------- | ------- | ---------------------------------- |
| price      | uint256 | Rounded midpoint.                  |
| highestBid | uint256 | Best bid across orderbook and AMM. |
| lowestAsk  | uint256 | Best ask across orderbook and AMM. |

#### getPriceLevel

```solidity
function getPriceLevel(address market, uint256 price) external view returns (PriceLevel)
```

Returns the resting size and queue pointers at a single price.

**Parameters**

| Name   | Type    | Description          |
| ------ | ------- | -------------------- |
| market | address | Market address.      |
| price  | uint256 | Price level to read. |

**Return Values**

| Name           | Type    | Description                                           |
| -------------- | ------- | ----------------------------------------------------- |
| size           | uint256 | Total resting size at this price.                     |
| latestNativeId | uint256 | Most recently assigned native order id at this price. |
| latest         | uint256 | Pointer to the newest order in the queue.             |
| fillNext       | uint256 | Pointer to the next order to be filled.               |

#### getPriceLevels

```solidity
function getPriceLevels(address market, bool isAscending, uint256 startPrice, uint256 distance, uint256 interval, uint256 max) external returns (bytes)
```

Walks the book from `startPrice` and aggregates resting size into price buckets. Returns packed 32-byte words, each `(bucketPrice << 128) | size`. Ask buckets round prices up to the interval; bid buckets round down.

**Parameters**

| Name        | Type    | Description                                            |
| ----------- | ------- | ------------------------------------------------------ |
| market      | address | Market address.                                        |
| isAscending | bool    | True to walk asks upward, false to walk bids downward. |
| startPrice  | uint256 | Price to start from.                                   |
| distance    | uint256 | Maximum distance from `startPrice` in ticks.           |
| interval    | uint256 | Bucket width in price units.                           |
| max         | uint256 | Maximum buckets to return (0 = unlimited).             |

**Return Values**

| Name | Type  | Description          |
| ---- | ----- | -------------------- |
| —    | bytes | Packed bucket words. |

#### getPriceLevelsFromMid

```solidity
function getPriceLevelsFromMid(address market, uint256 distance, uint256 interval, uint256 max) external returns (uint256 highestBid, uint256 lowestAsk, bytes bids, bytes asks)
```

Same as `getPriceLevels` but scans both sides from the current best bid and ask. Bucket encoding is identical.

**Parameters**

| Name     | Type    | Description                               |
| -------- | ------- | ----------------------------------------- |
| market   | address | Market address.                           |
| distance | uint256 | Ticks to scan on each side.               |
| interval | uint256 | Bucket width in price units.              |
| max      | uint256 | Maximum buckets per side (0 = unlimited). |

**Return Values**

| Name       | Type    | Description              |
| ---------- | ------- | ------------------------ |
| highestBid | uint256 | Current best bid.        |
| lowestAsk  | uint256 | Current best ask.        |
| bids       | bytes   | Packed bid bucket words. |
| asks       | bytes   | Packed ask bucket words. |

#### getReserves

```solidity
function getReserves(address market) external returns (uint112 reserveQuote, uint112 reserveBase)
```

Returns the AMM reserves for a market. Both are zero for markets without an AMM.

**Parameters**

| Name   | Type    | Description     |
| ------ | ------- | --------------- |
| market | address | Market address. |

**Return Values**

| Name         | Type    | Description          |
| ------------ | ------- | -------------------- |
| reserveQuote | uint112 | Quote asset reserve. |
| reserveBase  | uint112 | Base asset reserve.  |

#### getQuote

```solidity
function getQuote(address market, bool isBuy, bool isExactInput, bool isCompleteFill, uint256 size, uint256 worstPrice) external returns (uint256 amountIn, uint256 amountOut)
```

Quotes a single-market trade. Quotes are computed per price level rather than per order, so they can differ slightly from execution.

**Parameters**

| Name           | Type    | Description                                                           |
| -------------- | ------- | --------------------------------------------------------------------- |
| market         | address | Market address.                                                       |
| isBuy          | bool    | True to buy base with quote.                                          |
| isExactInput   | bool    | True if `size` is the input amount, false if it is the output amount. |
| isCompleteFill | bool    | True to revert if the full size cannot be filled within `worstPrice`. |
| size           | uint256 | Input or output amount.                                               |
| worstPrice     | uint256 | Inclusive price limit.                                                |

**Return Values**

| Name      | Type    | Description                     |
| --------- | ------- | ------------------------------- |
| amountIn  | uint256 | Input required, including fees. |
| amountOut | uint256 | Output expected, after fees.    |

#### getAmountsOut

```solidity
function getAmountsOut(uint256 amountIn, address[] path) external returns (uint256[] amounts, bool isPartialFill)
```

Quotes the expected output at each hop for an exact-input route. Reverts if the path has fewer than two assets or any hop lacks a routable market. Quotes are computed per price level and may differ slightly from execution.

**Parameters**

| Name     | Type       | Description                     |
| -------- | ---------- | ------------------------------- |
| amountIn | uint256    | Input amount for the first hop. |
| path     | address\[] | Asset route. See Conventions.   |

**Return Values**

| Name          | Type       | Description                                                        |
| ------------- | ---------- | ------------------------------------------------------------------ |
| amounts       | uint256\[] | `amounts[0]` is `amountIn`; `amounts[i]` is the output of hop `i`. |
| isPartialFill | bool       | True if any hop cannot consume the full input at that hop.         |

#### getAmountsIn

```solidity
function getAmountsIn(uint256 amountOut, address[] path) public returns (uint256[] amounts)
```

Quotes the input required at each hop for an exact-output route. Reverts if any hop cannot supply the requested output. Same approximation caveat as `getAmountsOut`.

**Parameters**

| Name      | Type       | Description                       |
| --------- | ---------- | --------------------------------- |
| amountOut | uint256    | Desired output from the last hop. |
| path      | address\[] | Asset route. See Conventions.     |

**Return Values**

| Name    | Type       | Description                                                                    |
| ------- | ---------- | ------------------------------------------------------------------------------ |
| amounts | uint256\[] | `amounts[path.length - 1]` is `amountOut`; `amounts[0]` is the input required. |

## Launchpad

Tokens created through `createToken` trade on a virtual bonding curve against native ETH until the curve's native reserve reaches its graduation threshold. At that point the token's orderbook market becomes routable and the remaining supply seeds its AMM.

#### createToken

```solidity
function createToken(string name, string symbol, string metadataCID, string description, string social1, string social2, string social3, string social4) external payable returns (address token)
```

Deploys a new token and its market. Until graduation, the pair is registered to a placeholder and is not routable through `swap` or the path swaps; use `buy` and `sell`. If `msg.value > 0`, creation is immediately followed by an exact-input `buy` with that ETH.

**Parameters**

| Name        | Type   | Description                     |
| ----------- | ------ | ------------------------------- |
| name        | string | Token name. Must be nonempty.   |
| symbol      | string | Token symbol. Must be nonempty. |
| metadataCID | string | IPFS CID for token metadata.    |
| description | string | Token description.              |
| social1     | string | Social link.                    |
| social2     | string | Social link.                    |
| social3     | string | Social link.                    |
| social4     | string | Social link.                    |

**Return Values**

| Name  | Type    | Description                   |
| ----- | ------- | ----------------------------- |
| token | address | Address of the created token. |

#### getVirtualReserves

```solidity
function getVirtualReserves(address token) external view returns (uint256 virtualNativeReserve, uint256 virtualTokenReserve)
```

Returns the active bonding-curve reserves. Both are zero once the token has graduated, since the launchpad state is deleted.

**Parameters**

| Name  | Type    | Description      |
| ----- | ------- | ---------------- |
| token | address | Launchpad token. |

**Return Values**

| Name                 | Type    | Description             |
| -------------------- | ------- | ----------------------- |
| virtualNativeReserve | uint256 | Virtual native reserve. |
| virtualTokenReserve  | uint256 | Virtual token reserve.  |

#### buy

```solidity
function buy(bool isExactInput, address token, uint256 amountIn, uint256 amountOut) external payable returns (uint256 inputAmount, uint256 outputAmount, bool isGraduated)
```

Buys a launchpad token with native ETH. If the purchase crosses the graduation threshold, the remainder is filled through the graduated orderbook market in the same call. After graduation, buys route entirely through the orderbook. Unused ETH is refunded.

**Parameters**

| Name         | Type    | Description                                                                                                                   |
| ------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------- |
| isExactInput | bool    | True for exact-input mode, false for exact-output mode.                                                                       |
| token        | address | Launchpad token.                                                                                                              |
| amountIn     | uint256 | Exact input when `isExactInput` is true (must equal `msg.value`). Otherwise the maximum acceptable input; 0 disables the cap. |
| amountOut    | uint256 | Minimum acceptable output when `isExactInput` is true. Otherwise the exact requested output.                                  |

**Return Values**

| Name         | Type    | Description                                     |
| ------------ | ------- | ----------------------------------------------- |
| inputAmount  | uint256 | ETH actually spent.                             |
| outputAmount | uint256 | Tokens actually received.                       |
| isGraduated  | bool    | True if the token is graduated after this call. |

#### sell

```solidity
function sell(bool isExactInput, address token, uint256 amountIn, uint256 amountOut) external returns (uint256 inputAmount, uint256 outputAmount)
```

Sells a launchpad token for native ETH. Before graduation the sale is against the bonding curve; after graduation it is a market order on the orderbook.

**Parameters**

| Name         | Type    | Description                                                                                                  |
| ------------ | ------- | ------------------------------------------------------------------------------------------------------------ |
| isExactInput | bool    | True for exact-input mode, false for exact-output mode.                                                      |
| token        | address | Launchpad token.                                                                                             |
| amountIn     | uint256 | Exact tokens to sell when `isExactInput` is true. Otherwise the maximum tokens to spend; 0 disables the cap. |
| amountOut    | uint256 | Minimum acceptable ETH when `isExactInput` is true. Otherwise the exact ETH requested.                       |

**Return Values**

| Name         | Type    | Description            |
| ------------ | ------- | ---------------------- |
| inputAmount  | uint256 | Tokens actually sold.  |
| outputAmount | uint256 | ETH actually received. |

#### quoteBuy

```solidity
function quoteBuy(bool isExactInput, address token, uint256 amountIn, uint256 amountOut) external returns (uint256 inputAmount, uint256 outputAmount, bool graduates)
```

Quotes `buy` without changing state. Same parameters and slippage semantics as `buy`.

**Parameters**

| Name         | Type    | Description                                                      |
| ------------ | ------- | ---------------------------------------------------------------- |
| isExactInput | bool    | True for exact-input mode, false for exact-output mode.          |
| token        | address | Launchpad token.                                                 |
| amountIn     | uint256 | Exact input, or maximum input in exact-output mode (0 = no cap). |
| amountOut    | uint256 | Minimum output, or exact output in exact-output mode.            |

**Return Values**

| Name         | Type    | Description                                                    |
| ------------ | ------- | -------------------------------------------------------------- |
| inputAmount  | uint256 | Estimated ETH required.                                        |
| outputAmount | uint256 | Estimated tokens received.                                     |
| graduates    | bool    | True if the quoted trade would cross the graduation threshold. |

#### quoteSell

```solidity
function quoteSell(bool isExactInput, address token, uint256 amountIn, uint256 amountOut) external returns (uint256 inputAmount, uint256 outputAmount)
```

Quotes `sell` without changing state. Same parameters and slippage semantics as `sell`.

**Parameters**

| Name         | Type    | Description                                                         |
| ------------ | ------- | ------------------------------------------------------------------- |
| isExactInput | bool    | True for exact-input mode, false for exact-output mode.             |
| token        | address | Launchpad token.                                                    |
| amountIn     | uint256 | Exact tokens to sell, or maximum in exact-output mode (0 = no cap). |
| amountOut    | uint256 | Minimum ETH, or exact ETH in exact-output mode.                     |

**Return Values**

| Name         | Type    | Description                |
| ------------ | ------- | -------------------------- |
| inputAmount  | uint256 | Estimated tokens required. |
| outputAmount | uint256 | Estimated ETH received.    |

## Orders

#### getOrder

```solidity
function getOrder(address market, uint256 price, uint256 id) external view returns (Order)
```

Returns an order by market, price, and native id.

**Parameters**

| Name   | Type    | Description      |
| ------ | ------- | ---------------- |
| market | address | Market address.  |
| price  | uint256 | Order price.     |
| id     | uint256 | Native order id. |

**Return Values**

| Name       | Type    | Description                                                       |
| ---------- | ------- | ----------------------------------------------------------------- |
| isBuy      | bool    | True if the order is a bid.                                       |
| market     | address | Market address.                                                   |
| price      | uint256 | Resting price.                                                    |
| size       | uint256 | Remaining size.                                                   |
| orderType  | uint256 | 1 if collateral came from internal balance, 0 if from a transfer. |
| userId     | uint256 | Owner's user id.                                                  |
| fillBefore | uint256 | Queue pointer to the previous order.                              |
| fillAfter  | uint256 | Queue pointer to the next order.                                  |

#### getOrderByCloid

```solidity
function getOrderByCloid(uint256 userId, uint256 cloid) public view returns (Order)
```

Returns an order by owner user id and client order id. Same return struct as `getOrder`.

**Parameters**

| Name   | Type    | Description               |
| ------ | ------- | ------------------------- |
| userId | uint256 | Owner's user id.          |
| cloid  | uint256 | Client order id (1–1023). |

#### getAllOrdersByCloid

```solidity
function getAllOrdersByCloid(address user, uint256 range) external view returns (uint256[] cloids, Order[] userOrders)
```

Scans cloid slots 1 to `range - 1` (capped at 1023) and returns every active order.

**Parameters**

| Name  | Type    | Description                         |
| ----- | ------- | ----------------------------------- |
| user  | address | Owner address.                      |
| range | uint256 | Upper bound of cloid slots to scan. |

**Return Values**

| Name       | Type       | Description                              |
| ---------- | ---------- | ---------------------------------------- |
| cloids     | uint256\[] | Cloids with an active order.             |
| userOrders | Order\[]   | Order struct for each cloid, same index. |

#### placeLimitOrder

```solidity
function placeLimitOrder(address tokenIn, address tokenOut, uint256 price, uint256 size, uint256 deadline) external payable returns (uint256 id)
```

Places a limit order on the canonical market for the pair, pulling collateral from the caller's wallet. If `tokenIn == eth`, `msg.value` must equal `size`.

**Parameters**

| Name     | Type    | Description                  |
| -------- | ------- | ---------------------------- |
| tokenIn  | address | Asset you are selling.       |
| tokenOut | address | Asset you are buying.        |
| price    | uint256 | Limit price.                 |
| size     | uint256 | Amount of `tokenIn` to rest. |
| deadline | uint256 | Expiry timestamp.            |

**Return Values**

| Name | Type    | Description      |
| ---- | ------- | ---------------- |
| id   | uint256 | Native order id. |

#### cancelLimitOrder

```solidity
function cancelLimitOrder(address tokenIn, address tokenOut, uint256 price, uint256 id, uint256 deadline) external returns (uint256 size)
```

Cancels a resting order on the canonical market and returns collateral to the caller. Reverts if the order is not found. If `tokenIn == eth`, collateral is unwrapped and sent as native ETH.

**Parameters**

| Name     | Type    | Description                                 |
| -------- | ------- | ------------------------------------------- |
| tokenIn  | address | Asset the order is selling.                 |
| tokenOut | address | Asset the order is buying.                  |
| price    | uint256 | Order price. 0 to look up by cloid.         |
| id       | uint256 | Native order id, or cloid when `price = 0`. |
| deadline | uint256 | Expiry timestamp.                           |

**Return Values**

| Name | Type    | Description          |
| ---- | ------- | -------------------- |
| size | uint256 | Collateral released. |

#### replaceLimitOrder

```solidity
function replaceLimitOrder(bool isPostOnly, bool isDecrease, address tokenIn, address tokenOut, uint256 price, uint256 id, uint256 newPrice, uint256 newSize, uint256 deadline, address referrer) external payable returns (uint256)
```

Cancels an existing order and reposts it. If the cancel finds nothing, no new order is placed. Additional collateral, if `newSize` exceeds the released amount, is pulled from the caller (`msg.value` when `tokenIn == eth`). Excess is refunded.

**Parameters**

| Name       | Type    | Description                                                                                                                                     |
| ---------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| isPostOnly | bool    | True to rest the replacement. False to submit it as a market-to-limit order that may take liquidity.                                            |
| isDecrease | bool    | True to reduce the order in place instead of cancel-and-repost. Also triggered automatically when `newPrice == price` and `newSize` is smaller. |
| tokenIn    | address | Asset the order is selling.                                                                                                                     |
| tokenOut   | address | Asset the order is buying.                                                                                                                      |
| price      | uint256 | Existing order price. 0 to look up by cloid.                                                                                                    |
| id         | uint256 | Native order id, or cloid when `price = 0`.                                                                                                     |
| newPrice   | uint256 | New price. 0 keeps the existing price.                                                                                                          |
| newSize    | uint256 | New size. 0 keeps the remaining size.                                                                                                           |
| deadline   | uint256 | Expiry timestamp.                                                                                                                               |
| referrer   | address | Referrer, used only if the replacement takes liquidity.                                                                                         |

**Return Values**

| Name | Type    | Description                                           |
| ---- | ------- | ----------------------------------------------------- |
| —    | uint256 | Id of the replacement order, or 0 if none was placed. |

## Swaps

All swap methods apply the `path` conventions above and return `amounts`, the amount moved at each hop (`amounts[0]` is the input, `amounts[path.length - 1]` is the output).

#### swapExactETHForTokens

```solidity
function swapExactETHForTokens(uint256 amountOutMin, address[] path, address to, uint256 deadline, address referrer) external payable returns (uint256[] amounts)
```

Swaps exactly `msg.value` of native ETH for as many output tokens as possible. `path` must start with `eth` and must not end with `eth`.

**Parameters**

| Name         | Type       | Description                         |
| ------------ | ---------- | ----------------------------------- |
| amountOutMin | uint256    | Revert if the output is below this. |
| path         | address\[] | Asset route starting with `eth`.    |
| to           | address    | Recipient of the output.            |
| deadline     | uint256    | Expiry timestamp.                   |
| referrer     | address    | Optional referrer.                  |

**Return Values**

| Name    | Type       | Description               |
| ------- | ---------- | ------------------------- |
| amounts | uint256\[] | Amount moved at each hop. |

#### swapExactTokensForETH

```solidity
function swapExactTokensForETH(uint256 amountIn, uint256 amountOutMin, address[] path, address to, uint256 deadline, address referrer) external returns (uint256[] amounts)
```

Swaps exactly `amountIn` tokens for as much native ETH as possible. `path` must end with `eth` and must not start with `eth`.

**Parameters**

| Name         | Type       | Description                             |
| ------------ | ---------- | --------------------------------------- |
| amountIn     | uint256    | Exact input of `path[0]`.               |
| amountOutMin | uint256    | Revert if the ETH output is below this. |
| path         | address\[] | Asset route ending with `eth`.          |
| to           | address    | Recipient of the ETH.                   |
| deadline     | uint256    | Expiry timestamp.                       |
| referrer     | address    | Optional referrer.                      |

**Return Values**

| Name    | Type       | Description               |
| ------- | ---------- | ------------------------- |
| amounts | uint256\[] | Amount moved at each hop. |

#### swapExactTokensForTokens

```solidity
function swapExactTokensForTokens(uint256 amountIn, uint256 amountOutMin, address[] path, address to, uint256 deadline, address referrer) external returns (uint256[] amounts)
```

Swaps exactly `amountIn` of `path[0]` for as much of the final asset as possible. `path` must not start or end with `eth`.

**Parameters**

| Name         | Type       | Description                         |
| ------------ | ---------- | ----------------------------------- |
| amountIn     | uint256    | Exact input of `path[0]`.           |
| amountOutMin | uint256    | Revert if the output is below this. |
| path         | address\[] | Asset route with no `eth`.          |
| to           | address    | Recipient of the output.            |
| deadline     | uint256    | Expiry timestamp.                   |
| referrer     | address    | Optional referrer.                  |

**Return Values**

| Name    | Type       | Description               |
| ------- | ---------- | ------------------------- |
| amounts | uint256\[] | Amount moved at each hop. |

#### swapETHForExactTokens

```solidity
function swapETHForExactTokens(uint256 amountOut, address[] path, address to, uint256 deadline, address referrer) external payable returns (uint256[] amounts)
```

Spends the minimum ETH needed to receive exactly `amountOut`. `msg.value` is the maximum you are willing to spend; unused ETH is refunded. `path` must start with `eth`.

**Parameters**

| Name      | Type       | Description                      |
| --------- | ---------- | -------------------------------- |
| amountOut | uint256    | Exact output required.           |
| path      | address\[] | Asset route starting with `eth`. |
| to        | address    | Recipient of the output.         |
| deadline  | uint256    | Expiry timestamp.                |
| referrer  | address    | Optional referrer.               |

**Return Values**

| Name    | Type       | Description                                                       |
| ------- | ---------- | ----------------------------------------------------------------- |
| amounts | uint256\[] | Amount moved at each hop. `amounts[0]` is the ETH actually spent. |

#### swapTokensForExactETH

```solidity
function swapTokensForExactETH(uint256 amountOut, uint256 amountInMax, address[] path, address to, uint256 deadline, address referrer) external returns (uint256[] amounts)
```

Spends up to `amountInMax` tokens to receive exactly `amountOut` native ETH. `path` must end with `eth`.

**Parameters**

| Name        | Type       | Description                         |
| ----------- | ---------- | ----------------------------------- |
| amountOut   | uint256    | Exact ETH output required.          |
| amountInMax | uint256    | Revert if more than this is needed. |
| path        | address\[] | Asset route ending with `eth`.      |
| to          | address    | Recipient of the ETH.               |
| deadline    | uint256    | Expiry timestamp.                   |
| referrer    | address    | Optional referrer.                  |

**Return Values**

| Name    | Type       | Description               |
| ------- | ---------- | ------------------------- |
| amounts | uint256\[] | Amount moved at each hop. |

#### swapTokensForExactTokens

```solidity
function swapTokensForExactTokens(uint256 amountOut, uint256 amountInMax, address[] path, address to, uint256 deadline, address referrer) external returns (uint256[] amounts)
```

Spends up to `amountInMax` of `path[0]` to receive exactly `amountOut` of the final asset. `path` must not start or end with `eth`.

**Parameters**

| Name        | Type       | Description                         |
| ----------- | ---------- | ----------------------------------- |
| amountOut   | uint256    | Exact output required.              |
| amountInMax | uint256    | Revert if more than this is needed. |
| path        | address\[] | Asset route with no `eth`.          |
| to          | address    | Recipient of the output.            |
| deadline    | uint256    | Expiry timestamp.                   |
| referrer    | address    | Optional referrer.                  |

**Return Values**

| Name    | Type       | Description               |
| ------- | ---------- | ------------------------- |
| amounts | uint256\[] | Amount moved at each hop. |

#### swap

```solidity
function swap(bool isExactInput, address tokenIn, address tokenOut, uint256 orderType, uint256 size, uint256 worstPrice, uint256 deadline, address referrer) external payable returns (uint256 amountIn, uint256 amountOut, uint256 id)
```

Executes a single-market trade between `tokenIn` and `tokenOut` on the canonical market for the pair, with full control over order type and price limit. Not multi-hop. Native ETH is wrapped and unwrapped internally; leftover WETH is refunded as ETH.

**Parameters**

| Name         | Type    | Description                                                            |
| ------------ | ------- | ---------------------------------------------------------------------- |
| isExactInput | bool    | True if `size` is the input amount, false if it is the desired output. |
| tokenIn      | address | Asset you are selling, or `eth`.                                       |
| tokenOut     | address | Asset you are buying, or `eth`.                                        |
| orderType    | uint256 | See Order types (0–3).                                                 |
| size         | uint256 | Input or output amount.                                                |
| worstPrice   | uint256 | Inclusive price limit.                                                 |
| deadline     | uint256 | Expiry timestamp.                                                      |
| referrer     | address | Optional referrer.                                                     |

**Return Values**

| Name      | Type    | Description                                                   |
| --------- | ------- | ------------------------------------------------------------- |
| amountIn  | uint256 | Input consumed, including fees.                               |
| amountOut | uint256 | Output received, after fees.                                  |
| id        | uint256 | Id of the rested remainder when `orderType = 2`; otherwise 0. |

## Liquidity

#### addLiquidity

```solidity
function addLiquidity(address market, address to, uint256 amountQuoteDesired, uint256 amountBaseDesired, uint256 amountQuoteMin, uint256 amountBaseMin) external payable returns (uint256 liquidity)
```

Adds liquidity to a market's AMM and mints LP tokens (the market contract is the LP token) to `to`. Send `msg.value` to supply the WETH side as native ETH; the market must have WETH as one of its assets. Unused ETH is refunded.

**Parameters**

| Name               | Type    | Description                             |
| ------------------ | ------- | --------------------------------------- |
| market             | address | Market address.                         |
| to                 | address | Recipient of LP tokens.                 |
| amountQuoteDesired | uint256 | Maximum quote to add.                   |
| amountBaseDesired  | uint256 | Maximum base to add.                    |
| amountQuoteMin     | uint256 | Revert if less quote than this is used. |
| amountBaseMin      | uint256 | Revert if less base than this is used.  |

**Return Values**

| Name      | Type    | Description       |
| --------- | ------- | ----------------- |
| liquidity | uint256 | LP tokens minted. |

#### removeLiquidity

```solidity
function removeLiquidity(address market, address to, uint256 liquidity, uint256 amountQuoteMin, uint256 amountBaseMin) external returns (uint256 amountQuote, uint256 amountBase)
```

Burns `liquidity` LP tokens and sends both assets to `to`.

**Parameters**

| Name           | Type    | Description                                 |
| -------------- | ------- | ------------------------------------------- |
| market         | address | Market address.                             |
| to             | address | Recipient of the withdrawn assets.          |
| liquidity      | uint256 | LP tokens to burn.                          |
| amountQuoteMin | uint256 | Revert if less quote than this is returned. |
| amountBaseMin  | uint256 | Revert if less base than this is returned.  |

**Return Values**

| Name        | Type    | Description     |
| ----------- | ------- | --------------- |
| amountQuote | uint256 | Quote returned. |
| amountBase  | uint256 | Base returned.  |

#### removeLiquidityETH

```solidity
function removeLiquidityETH(address market, address to, uint256 liquidity, uint256 amountQuoteMin, uint256 amountBaseMin) external returns (uint256 amountQuote, uint256 amountBase)
```

Same as `removeLiquidity`, but the WETH side is unwrapped and sent as native ETH. Reverts if the market has no WETH side.

**Parameters**

| Name           | Type    | Description                                      |
| -------------- | ------- | ------------------------------------------------ |
| market         | address | Market address. Must have WETH as quote or base. |
| to             | address | Recipient of the withdrawn assets.               |
| liquidity      | uint256 | LP tokens to burn.                               |
| amountQuoteMin | uint256 | Revert if less quote than this is returned.      |
| amountBaseMin  | uint256 | Revert if less base than this is returned.       |

**Return Values**

| Name        | Type    | Description     |
| ----------- | ------- | --------------- |
| amountQuote | uint256 | Quote returned. |
| amountBase  | uint256 | Base returned.  |

## Rewards

Fees for referrers, market creators, and the protocol accumulate in `claimableRewards[token][account]`.

#### claimFees

```solidity
function claimFees(address to, address[] tokens) external returns (uint256[] amounts)
```

Transfers the caller's claimable balance for each token to `to`. Pass `eth` to receive the WETH balance as native ETH. Claiming also deletes any pending expired-fee claim queued against the caller.

**Parameters**

| Name   | Type       | Description      |
| ------ | ---------- | ---------------- |
| to     | address    | Recipient.       |
| tokens | address\[] | Tokens to claim. |

**Return Values**

| Name    | Type       | Description                           |
| ------- | ---------- | ------------------------------------- |
| amounts | uint256\[] | Amount claimed per token, same index. |

#### addClaimableFee

```solidity
function addClaimableFee(address to, address[] tokens, uint256[] amounts) external payable
```

Funds another account's claimable balance. For `eth` entries the amount is taken from `msg.value`; `msg.value` must equal the sum of all `eth` amounts. ERC-20 entries are pulled via `transferFrom`.

**Parameters**

| Name    | Type       | Description                   |
| ------- | ---------- | ----------------------------- |
| to      | address    | Account to credit.            |
| tokens  | address\[] | Tokens to credit.             |
| amounts | uint256\[] | Amount per token, same index. |

#### queueClaimExpiredFees

```solidity
function queueClaimExpiredFees(address user, address[] tokens) external
```

Starts the expiry clock on a user's unclaimed fees. Anyone can queue, but only one claim can be pending per user, every token must have a nonzero balance, and `tokens.length` must be under 100. After `feeClaimDuration`, governance can execute the claim. The user can cancel it at any time by calling `claimFees`.

**Parameters**

| Name   | Type       | Description                     |
| ------ | ---------- | ------------------------------- |
| user   | address    | Account with unclaimed fees.    |
| tokens | address\[] | Tokens to include in the claim. |

## Direct Trading

These methods address a market directly and expose balance routing, client order ids, STP, and forwarding. Integrators who only need token-pair trading should use the Swaps and Orders sections. All of them take `options`; see Execution Options.

#### marketOrder

```solidity
function marketOrder(address market, bool isBuy, bool isExactInput, uint256 options, uint256 orderType, uint256 size, uint256 worstPrice, address referrer, address user) external returns (uint256 amountIn, uint256 amountOut, uint256 id)
```

Executes a market order on a specific market.

**Parameters**

| Name         | Type    | Description                         |
| ------------ | ------- | ----------------------------------- |
| market       | address | Market address.                     |
| isBuy        | bool    | True to buy base with quote.        |
| isExactInput | bool    | True if `size` is the input amount. |
| options      | uint256 | Packed flags (marketOrder layout).  |
| orderType    | uint256 | See Order types (0–3).              |
| size         | uint256 | Input or output amount.             |
| worstPrice   | uint256 | Inclusive price limit.              |
| referrer     | address | Optional referrer.                  |
| user         | address | Account to trade as. 0 = caller.    |

**Return Values**

| Name      | Type    | Description                                                   |
| --------- | ------- | ------------------------------------------------------------- |
| amountIn  | uint256 | Input consumed, including fees.                               |
| amountOut | uint256 | Output received, after fees.                                  |
| id        | uint256 | Id of the rested remainder when `orderType = 2`; otherwise 0. |

#### limitOrder

```solidity
function limitOrder(address market, bool isBuy, uint256 options, uint256 price, uint256 size, address user) external returns (uint256 id)
```

Rests a limit order on a specific market. Reverts if the order would cross the spread or AMM, is below `minSize`, or reuses an active cloid.

**Parameters**

| Name    | Type    | Description                                |
| ------- | ------- | ------------------------------------------ |
| market  | address | Market address.                            |
| isBuy   | bool    | True for a bid, false for an ask.          |
| options | uint256 | Packed flags (limitOrder layout).          |
| price   | uint256 | Limit price.                               |
| size    | uint256 | Order size: quote for bids, base for asks. |
| user    | address | Order owner. 0 = caller.                   |

**Return Values**

| Name | Type    | Description                                        |
| ---- | ------- | -------------------------------------------------- |
| id   | uint256 | Native order id, or the cloid if one was supplied. |

#### cancelOrder

```solidity
function cancelOrder(address market, uint256 options, uint256 price, uint256 id, address user) external returns (uint256 size)
```

Cancels a resting order. Returns 0 without reverting if the order is not found.

**Parameters**

| Name    | Type    | Description                                 |
| ------- | ------- | ------------------------------------------- |
| market  | address | Market address.                             |
| options | uint256 | Packed flags (cancelOrder layout).          |
| price   | uint256 | Order price. 0 to look up by cloid.         |
| id      | uint256 | Native order id, or cloid when `price = 0`. |
| user    | address | Order owner. 0 = caller.                    |

**Return Values**

| Name | Type    | Description          |
| ---- | ------- | -------------------- |
| size | uint256 | Collateral released. |

#### replaceOrder

```solidity
function replaceOrder(address market, uint256 options, uint256 price, uint256 id, uint256 newPrice, uint256 size, address referrer, address user) external returns (uint256 _id)
```

Cancels and reposts, or decreases, an existing order with netted settlement. Same price with a smaller size, or the `isDecrease` flag, reduces in place. Whether the replacement is post-only or may take is controlled by the `allowTaker` option bit. Returns 0 if nothing changed.

**Parameters**

| Name     | Type    | Description                                             |
| -------- | ------- | ------------------------------------------------------- |
| market   | address | Market address.                                         |
| options  | uint256 | Packed flags (replaceOrder layout).                     |
| price    | uint256 | Existing order price. 0 to look up by cloid.            |
| id       | uint256 | Native order id, or cloid when `price = 0`.             |
| newPrice | uint256 | New price. 0 keeps the existing price.                  |
| size     | uint256 | New size. 0 keeps the remaining size.                   |
| referrer | address | Referrer, used only if the replacement takes liquidity. |
| user     | address | Order owner. 0 = caller.                                |

**Return Values**

| Name | Type    | Description                                           |
| ---- | ------- | ----------------------------------------------------- |
| \_id | uint256 | Id of the replacement order, or 0 if none was placed. |

#### batchOrders

```solidity
function batchOrders(address market, Action[] actions, uint256 options, uint256 deadline, address referrer, address user) external payable
```

Executes a sequence of actions on one market with a single netted settlement. Send `msg.value` to fund the WETH side from native ETH; unused ETH is refunded. See Batch Actions for encoding.

**Parameters**

| Name     | Type      | Description                                  |
| -------- | --------- | -------------------------------------------- |
| market   | address   | Market address.                              |
| actions  | Action\[] | Actions to execute in order.                 |
| options  | uint256   | Packed flags (batchOrders layout).           |
| deadline | uint256   | Expiry timestamp.                            |
| referrer | address   | Referrer for any market orders in the batch. |
| user     | address   | Account to act as. 0 = caller.               |

#### multiBatchOrders

```solidity
function multiBatchOrders(Batch[] batches, uint256 deadline, address referrer) external payable
```

Runs `batchOrders` across several markets in one transaction. Always acts as `msg.sender`.

```solidity
struct Batch {
    address market;
    Action[] actions;
    uint256 options;
}
```

**Parameters**

| Name     | Type     | Description                                                  |
| -------- | -------- | ------------------------------------------------------------ |
| batches  | Batch\[] | One entry per market, each with its own actions and options. |
| deadline | uint256  | Expiry timestamp.                                            |
| referrer | address  | Referrer for any market orders.                              |

#### fallback

The contract's fallback accepts raw calldata for gas-minimal multi-market batching. The caller must already be registered. Each market segment is a header word, `(balanceMode << 252) | (bid << 172) | (actionCount << 160) | market`, followed by `actionCount` packed action words. `bid` (80 bits) is an optional native-token tip paid to `block.coinbase` if the batch succeeds. Any `msg.value` left after the batch is refunded. See the fallback encoding reference for the action word layout.

## Accounts

#### registerUser

```solidity
function registerUser(address user) external returns (uint256 userId)
```

Assigns a user id. `user` must equal `msg.sender`. Registration also happens automatically on first `deposit`, `limitOrder`, `marketOrder`, `replaceOrder`, or `batchOrders`. User id 0 is reserved for the router.

**Parameters**

| Name | Type    | Description                              |
| ---- | ------- | ---------------------------------------- |
| user | address | Address to register. Must be the caller. |

**Return Values**

| Name   | Type    | Description       |
| ------ | ------- | ----------------- |
| userId | uint256 | Assigned user id. |

#### getDepositedBalance

```solidity
function getDepositedBalance(address user, address asset) external view returns (uint256 totalBalance, uint256 availableBalance, uint256 lockedBalance)
```

Returns a user's deposited balance for an asset. Native deposits are stored as WETH, and this getter does not normalize: query with `weth`, not `eth`. Returns zeros for unregistered users.

**Parameters**

| Name  | Type    | Description                                    |
| ----- | ------- | ---------------------------------------------- |
| user  | address | User address.                                  |
| asset | address | Token address. Use `weth` for native deposits. |

**Return Values**

| Name             | Type    | Description                                                       |
| ---------------- | ------- | ----------------------------------------------------------------- |
| totalBalance     | uint256 | `availableBalance + lockedBalance`.                               |
| availableBalance | uint256 | Withdrawable balance.                                             |
| lockedBalance    | uint256 | Collateral locked in resting orders placed from internal balance. |

#### deposit

```solidity
function deposit(address token, uint256 amount) external payable returns (uint256 userId)
```

Deposits into the caller's internal balance and registers the caller if needed. For `token == eth`, `msg.value` must equal `amount` and the balance is stored as WETH. For ERC-20s, `msg.value` must be zero and the contract pulls `amount` via `transferFrom`.

**Parameters**

| Name   | Type    | Description                 |
| ------ | ------- | --------------------------- |
| token  | address | Token to deposit, or `eth`. |
| amount | uint256 | Amount to deposit.          |

**Return Values**

| Name   | Type    | Description       |
| ------ | ------- | ----------------- |
| userId | uint256 | Caller's user id. |

#### withdraw

```solidity
function withdraw(address to, address token, uint256 amount) external
```

Withdraws from the caller's available (unlocked) balance. `amount = 0` withdraws the full available balance; locked collateral is never withdrawn. Pass `eth` to receive native ETH.

**Parameters**

| Name   | Type    | Description                                     |
| ------ | ------- | ----------------------------------------------- |
| to     | address | Recipient.                                      |
| token  | address | Token to withdraw, or `eth`.                    |
| amount | uint256 | Amount to withdraw. 0 = full available balance. |

#### approveForwarder

```solidity
function approveForwarder(address forwarder) external
```

Authorizes `forwarder` to act as `msg.sender` in any method that takes a `user` parameter. A forwarder can trade and redirect outputs, which is equivalent to full control of your assets on Crystal.

**Parameters**

| Name      | Type    | Description           |
| --------- | ------- | --------------------- |
| forwarder | address | Address to authorize. |

#### removeForwarder

```solidity
function removeForwarder(address forwarder) external
```

Revokes a forwarder approval.

**Parameters**

| Name      | Type    | Description        |
| --------- | ------- | ------------------ |
| forwarder | address | Address to revoke. |

**Warning.** Slot 0 is a shared temporary balance used by the router during multi-hop swaps and native wrapping. It is not tied to any account: anyone can withdraw whatever is in it. Only use these methods inside an atomic transaction that consumes or withdraws the balance before it ends.

#### routerDeposit

```solidity
function routerDeposit(address token, uint256 amount) external payable
```

Deposits into slot 0. For `eth`, `msg.value` must equal `amount`; otherwise `msg.value` must be zero and the token is pulled via `transferFrom`. Pair with the `fromInternal` option bit on advanced methods.

**Parameters**

| Name   | Type    | Description                 |
| ------ | ------- | --------------------------- |
| token  | address | Token to deposit, or `eth`. |
| amount | uint256 | Amount to deposit.          |

#### routerWithdraw

```solidity
function routerWithdraw(address to, address token, uint256 amount) external
```

Withdraws from slot 0 to `to`. Pass `eth` to receive native ETH.

**Parameters**

| Name   | Type    | Description                                          |
| ------ | ------- | ---------------------------------------------------- |
| to     | address | Recipient.                                           |
| token  | address | Token to withdraw, or `eth`.                         |
| amount | uint256 | Amount to withdraw. 0 = full available slot balance. |

## Governance

Callable only by `gov` unless noted.

#### deploy

```solidity
function deploy(bool isCanonical, address quoteAsset, address baseAsset, uint256 marketType, uint256 scaleFactor, uint256 tickSize, uint256 maxPrice, uint256 minSize, uint24 takerFee, uint24 makerRebate) external returns (address market)
```

Deploys a new market. Anyone can deploy a non-canonical market; `isCanonical` requires `isCanonicalDeployer`. A non-canonical type 1 or 2 market becomes canonical automatically if the pair has none. `eth` is normalized to `weth`. Reverts if either asset is an ungraduated launchpad token.

**Parameters**

| Name        | Type    | Description                                                                            |
| ----------- | ------- | -------------------------------------------------------------------------------------- |
| isCanonical | bool    | Register as the canonical market for the pair. Requires canonical-deployer permission. |
| quoteAsset  | address | Quote asset, or `eth`.                                                                 |
| baseAsset   | address | Base asset, or `eth`.                                                                  |
| marketType  | uint256 | Market type (0–4). Types 2–4 enable the AMM.                                           |
| scaleFactor | uint256 | Price scale factor.                                                                    |
| tickSize    | uint256 | Minimum price increment. `maxPrice` must be a multiple of it.                          |
| maxPrice    | uint256 | Maximum price.                                                                         |
| minSize     | uint256 | Minimum resting limit order size.                                                      |
| takerFee    | uint24  | Taker fee, scaled by 1e5. Between 90000 and 100000.                                    |
| makerRebate | uint24  | Maker rebate, scaled by 1e5. At least 99800 for AMM markets, 90000 otherwise.          |

**Return Values**

| Name   | Type    | Description              |
| ------ | ------- | ------------------------ |
| market | address | Deployed market address. |

#### changeMarketParams

```solidity
function changeMarketParams(address market, uint256 newMinSize, uint24 newTakerFee, uint24 newMakerRebate, bool isAMMEnabled, bool isCanonical) external
```

Updates fees, minimum size, AMM state, and canonical status. Caller must be a canonical deployer or `gov`, and must be the market creator or `gov`. Setting `isCanonical` only takes effect if the pair has no canonical market or the caller is `gov`.

**Parameters**

| Name           | Type    | Description                                          |
| -------------- | ------- | ---------------------------------------------------- |
| market         | address | Market address.                                      |
| newMinSize     | uint256 | New minimum resting order size.                      |
| newTakerFee    | uint24  | New taker fee, scaled by 1e5.                        |
| newMakerRebate | uint24  | New maker rebate, scaled by 1e5.                     |
| isAMMEnabled   | bool    | Whether the AMM is enabled.                          |
| isCanonical    | bool    | Whether the market should be canonical for its pair. |

#### changeMarketCreatorFee

```solidity
function changeMarketCreatorFee(address market, address newCreator, uint256 newCreatorFee) external
```

Reassigns the market creator and their fee split. Caller must be the creator and a canonical deployer, or `gov`.

**Parameters**

| Name          | Type    | Description                                     |
| ------------- | ------- | ----------------------------------------------- |
| market        | address | Market address.                                 |
| newCreator    | address | New creator.                                    |
| newCreatorFee | uint256 | Creator's share of fees in percent. Maximum 50. |

#### changeGov

```solidity
function changeGov(address newGov) external
```

Transfers governance. The old `gov` loses canonical-deployer status and the new one gains it.

**Parameters**

| Name   | Type    | Description             |
| ------ | ------- | ----------------------- |
| newGov | address | New governance address. |

#### changeFeeRecipient

```solidity
function changeFeeRecipient(address newFeeRecipient) external
```

Sets the protocol fee recipient. Cannot be the zero address.

**Parameters**

| Name            | Type    | Description    |
| --------------- | ------- | -------------- |
| newFeeRecipient | address | New recipient. |

#### changeFeeClaimDuration

```solidity
function changeFeeClaimDuration(uint256 newFeeClaimDuration) external
```

Sets how long a queued expired-fee claim must wait before governance can execute it.

**Parameters**

| Name                | Type    | Description                         |
| ------------------- | ------- | ----------------------------------- |
| newFeeClaimDuration | uint256 | Duration in seconds. Minimum 86400. |

#### changeRefFeeCommission

```solidity
function changeRefFeeCommission(uint8 newFeeCommission) external
```

Sets the percentage of taker fees paid to referrers.

**Parameters**

| Name             | Type  | Description             |
| ---------------- | ----- | ----------------------- |
| newFeeCommission | uint8 | Percentage. Maximum 50. |

#### changeLaunchpadParams

```solidity
function changeLaunchpadParams(LaunchpadParams newLaunchpadParams) external
```

Replaces the global launchpad configuration. Applies to tokens created after the change.

```solidity
struct LaunchpadParams {
    uint112 launchpadInitialNativeSupply;
    uint256 launchpadFee;
    uint256 launchpadCreatorFeeSplit;
    uint256 graduatedMinSize;
    uint256 graduatedTakerFee;
    uint256 graduatedMakerRebate;
    uint256 graduatedCreatorFeeSplit;
}
```

**Parameters**

| Name                         | Type    | Description                                                          |
| ---------------------------- | ------- | -------------------------------------------------------------------- |
| launchpadInitialNativeSupply | uint112 | Initial virtual native reserve. Must exceed 1e18.                    |
| launchpadFee                 | uint256 | Bonding-curve fee, scaled by 1e5. Between 90000 and 100000.          |
| launchpadCreatorFeeSplit     | uint256 | Creator's share of curve fees in percent. Maximum 50.                |
| graduatedMinSize             | uint256 | Minimum order size for graduated markets.                            |
| graduatedTakerFee            | uint256 | Taker fee for graduated markets, scaled by 1e5.                      |
| graduatedMakerRebate         | uint256 | Maker rebate for graduated markets, scaled by 1e5. At least 99800.   |
| graduatedCreatorFeeSplit     | uint256 | Creator's share of fees on graduated markets in percent. Maximum 50. |

#### addCanonicalDeployer

```solidity
function addCanonicalDeployer(address deployer) external
```

Grants permission to deploy canonical markets.

**Parameters**

| Name     | Type    | Description           |
| -------- | ------- | --------------------- |
| deployer | address | Address to authorize. |

#### removeCanonicalDeployer

```solidity
function removeCanonicalDeployer(address deployer) external
```

Revokes canonical-deployer permission.

**Parameters**

| Name     | Type    | Description        |
| -------- | ------- | ------------------ |
| deployer | address | Address to revoke. |

#### executeClaimExpiredFees

```solidity
function executeClaimExpiredFees(address user) external returns (uint256[] amounts)
```

Executes a queued expired-fee claim once its deadline has passed, transferring the amounts recorded at queue time to `gov`.

**Parameters**

| Name | Type    | Description                            |
| ---- | ------- | -------------------------------------- |
| user | address | Account whose queued claim to execute. |

**Return Values**

| Name    | Type       | Description                                   |
| ------- | ---------- | --------------------------------------------- |
| amounts | uint256\[] | Amount claimed per token in the queued claim. |

#### claimLockedReserves

```solidity
function claimLockedReserves(address market) external returns (uint256)
```

For graduated launchpad markets, moves native-token AMM reserve that can never be withdrawn by traders into `gov`'s claimable rewards. Cumulative claims are tracked in `claimedLockedReserve`.

**Parameters**

| Name   | Type    | Description                 |
| ------ | ------- | --------------------------- |
| market | address | Graduated launchpad market. |

**Return Values**

| Name | Type    | Description                                     |
| ---- | ------- | ----------------------------------------------- |
| —    | uint256 | Amount moved to claimable rewards in this call. |

## State and Registry Getters

Autogenerated getters on public state. All are `view`.

| Name                    | Signature                                                                             | Description                                                                                                             |
| ----------------------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| weth                    | `weth() → address`                                                                    | Wrapped native token.                                                                                                   |
| eth                     | `eth() → address`                                                                     | Native-token sentinel, `0xEeee…EEeE`.                                                                                   |
| gov                     | `gov() → address`                                                                     | Governance address.                                                                                                     |
| feeRecipient            | `feeRecipient() → address`                                                            | Protocol fee recipient.                                                                                                 |
| feeCommission           | `feeCommission() → uint8`                                                             | Percentage of taker fees paid to referrers.                                                                             |
| feeClaimDuration        | `feeClaimDuration() → uint256`                                                        | Seconds before a queued fee claim can be executed.                                                                      |
| latestUserId            | `latestUserId() → uint256`                                                            | Most recently assigned user id.                                                                                         |
| userIdToAddress         | `userIdToAddress(uint256) → address`                                                  | User id to address. Id 0 is the router.                                                                                 |
| addressToUserId         | `addressToUserId(address) → uint256`                                                  | Address to user id; 0 if unregistered.                                                                                  |
| claimableRewards        | `claimableRewards(address token, address account) → uint256`                          | Claimable fee balance.                                                                                                  |
| pendingExpiredFeeClaims | `pendingExpiredFeeClaims(address) → uint256 deadline`                                 | Deadline of a queued expiry claim. The autogenerated getter returns only the deadline, not the token and amount arrays. |
| approvedForwarder       | `approvedForwarder(address user, address forwarder) → bool`                           | Whether `forwarder` may act for `user`.                                                                                 |
| isCanonicalDeployer     | `isCanonicalDeployer(address) → bool`                                                 | Whether an address may deploy canonical markets.                                                                        |
| marketIdToMarket        | `marketIdToMarket(uint256) → address`                                                 | Market id to address.                                                                                                   |
| getMarketByTokens       | `getMarketByTokens(address, address) → address`                                       | Canonical market for a pair, in either order. Returns the placeholder address for ungraduated launchpad tokens.         |
| allMarkets              | `allMarkets(uint256) → address`                                                       | Market at index.                                                                                                        |
| allTokens               | `allTokens(uint256) → address`                                                        | Launchpad token at index.                                                                                               |
| launchpadParams         | `launchpadParams() → (uint112, uint256, uint256, uint256, uint256, uint256, uint256)` | Global launchpad settings, in `LaunchpadParams` field order.                                                            |
| launchpadTokenToMarket  | `launchpadTokenToMarket(address) → (uint112, uint112, uint256, address, address)`     | Bonding-curve state: virtual native reserve, virtual token reserve, `k`, creator, market. Zeroed after graduation.      |
| wasLaunchpad            | `wasLaunchpad(address market) → bool`                                                 | Whether a market originated from launchpad graduation.                                                                  |
| claimedLockedReserve    | `claimedLockedReserve(address market) → uint256`                                      | Cumulative locked reserve claimed by governance.                                                                        |

***

## Events

Events and errors emitted by Crystal, with field encodings for indexers.

#### MarketCreated

```solidity
event MarketCreated(bool indexed isCanonical, address indexed quoteAsset, address indexed baseAsset, address market, address creator, TokenMetadata quoteInfo, TokenMetadata baseInfo, MarketDetails marketInfo)
```

Emitted by `deploy`, and again at launchpad graduation for the graduated market.

| Name        | Type          | Description                                                                                |
| ----------- | ------------- | ------------------------------------------------------------------------------------------ |
| isCanonical | bool          | Whether the market was registered as canonical. Always true at graduation.                 |
| quoteAsset  | address       | Quote asset.                                                                               |
| baseAsset   | address       | Base asset.                                                                                |
| market      | address       | Market address.                                                                            |
| creator     | address       | Market creator.                                                                            |
| quoteInfo   | TokenMetadata | Quote token `(token, decimals, ticker, name)`.                                             |
| baseInfo    | TokenMetadata | Base token `(token, decimals, ticker, name)`.                                              |
| marketInfo  | MarketDetails | `(marketId, marketType, scaleFactor, tickSize, maxPrice, minSize, takerFee, makerRebate)`. |

#### MarketParamsChanged

```solidity
event MarketParamsChanged(address indexed market, address creator, uint256 minSize, uint24 takerFee, uint24 makerRebate, uint8 creatorFee, bool isAMMEnabled, bool isCanonical)
```

Emitted by `changeMarketParams` and `changeMarketCreatorFee` with the market's full parameter set after the change.

| Name         | Type    | Description                                   |
| ------------ | ------- | --------------------------------------------- |
| market       | address | Market address.                               |
| creator      | address | Current creator.                              |
| minSize      | uint256 | Minimum order size.                           |
| takerFee     | uint24  | Taker fee, scaled by 1e5.                     |
| makerRebate  | uint24  | Maker rebate, scaled by 1e5.                  |
| creatorFee   | uint8   | Creator fee split in percent.                 |
| isAMMEnabled | bool    | Whether the AMM is enabled.                   |
| isCanonical  | bool    | Whether the market is canonical for its pair. |

#### LaunchpadParamsChanged

```solidity
event LaunchpadParamsChanged(uint112 launchpadInitialNativeSupply, uint256 launchpadFee, uint256 launchpadCreatorFeeSplit, uint256 graduatedMinSize, uint256 graduatedTakerFee, uint256 graduatedMakerRebate, uint256 graduatedCreatorFeeSplit)
```

Emitted by `changeLaunchpadParams`. Fields match `LaunchpadParams`.

#### GovChanged

```solidity
event GovChanged(address prev, address gov)
```

Emitted by `changeGov`.

| Name | Type    | Description                  |
| ---- | ------- | ---------------------------- |
| prev | address | Previous governance address. |
| gov  | address | New governance address.      |

#### UserRegistered

```solidity
event UserRegistered(address indexed user, uint256 indexed userId)
```

Emitted when an address is assigned a user id, whether by `registerUser` or automatically.

| Name   | Type    | Description         |
| ------ | ------- | ------------------- |
| user   | address | Registered address. |
| userId | uint256 | Assigned id.        |

#### Deposit

```solidity
event Deposit(address indexed user, uint256 indexed userId, address indexed token, uint256 amount)
```

Emitted by `deposit`. `token` is the stored asset, so native deposits report `weth`.

| Name   | Type    | Description          |
| ------ | ------- | -------------------- |
| user   | address | Depositor.           |
| userId | uint256 | Depositor's user id. |
| token  | address | Token deposited.     |
| amount | uint256 | Amount deposited.    |

#### Withdraw

```solidity
event Withdraw(address indexed user, uint256 indexed userId, address indexed token, uint256 amount)
```

Emitted by `withdraw`. `token` is the stored asset, so native withdrawals report `weth`.

| Name   | Type    | Description          |
| ------ | ------- | -------------------- |
| user   | address | Withdrawing account. |
| userId | uint256 | Their user id.       |
| token  | address | Token withdrawn.     |
| amount | uint256 | Amount withdrawn.    |

#### RewardsClaimed

```solidity
event RewardsClaimed(address indexed user, address[] tokens, uint256[] amounts)
```

Emitted by `claimFees`.

| Name    | Type       | Description                                                                  |
| ------- | ---------- | ---------------------------------------------------------------------------- |
| user    | address    | Claiming account.                                                            |
| tokens  | address\[] | Tokens claimed, as passed to `claimFees` (`eth` if the caller passed `eth`). |
| amounts | uint256\[] | Amount per token.                                                            |

#### Trade

```solidity
event Trade(address indexed market, address indexed user, bool isBuy, uint256 amountIn, uint256 amountOut, uint256 startPrice, uint256 endPrice)
```

Emitted once per market order that fills any amount. Covers `marketOrder`, `swap`, the path swaps (one per hop), launchpad `buy`/`sell` after graduation, batch market orders, and market-to-limit replacements.

| Name       | Type    | Description                     |
| ---------- | ------- | ------------------------------- |
| market     | address | Market traded.                  |
| user       | address | Taker.                          |
| isBuy      | bool    | True if the taker bought base.  |
| amountIn   | uint256 | Input consumed, including fees. |
| amountOut  | uint256 | Output received, after fees.    |
| startPrice | uint256 | Price of the first fill.        |
| endPrice   | uint256 | Price of the last fill.         |

#### Fill

```solidity
event Fill(address indexed market, address indexed user, uint256 fillInfo, uint256 fillAmount)
```

Emitted once per resting order that a taker fills, addressed to the maker.

| Name       | Type    | Description                                                                                                                                                                                        |
| ---------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| market     | address | Market.                                                                                                                                                                                            |
| user       | address | Maker whose order was filled.                                                                                                                                                                      |
| fillInfo   | uint256 | Packed: top 4 bits are 0 if the taker was buying, 1 if selling; bits 168–247 the order price; bits 112–167 the order id; bits 0–111 the order's remaining size after the fill (0 if fully filled). |
| fillAmount | uint256 | Packed: high 128 bits are what the maker received; low 128 bits are what the maker gave up.                                                                                                        |

#### OrdersUpdated

```solidity
event OrdersUpdated(address indexed market, address indexed user, bytes orderData)
```

Emitted whenever a user's resting orders change: placement, cancellation, decrease, market-to-limit remainder, and STP cancellations. `orderData` is a packed sequence of 32-byte words, one per affected order.

| Name      | Type    | Description                                                                                                                |
| --------- | ------- | -------------------------------------------------------------------------------------------------------------------------- |
| market    | address | Market.                                                                                                                    |
| user      | address | Order owner.                                                                                                               |
| orderData | bytes   | Packed words. Each word: top 4 bits are the update type; bits 168–247 the price; bits 112–162 the id; bits 0–111 the size. |

Update types:

| Value | Meaning       | Size field      |
| ----- | ------------- | --------------- |
| 0     | Bid cancelled | Size released.  |
| 1     | Ask cancelled | Size released.  |
| 2     | Bid placed    | Size resting.   |
| 3     | Ask placed    | Size resting.   |
| 4     | Bid decreased | Amount removed. |
| 5     | Ask decreased | Amount removed. |

The id field holds the native id for native orders, or `(cloid << 41) | userId` for cloid orders.

#### TokenCreated

```solidity
event TokenCreated(address indexed token, address indexed creator, string name, string symbol, string metadataCID, string description, string social1, string social2, string social3, string social4)
```

Emitted by `createToken`. Fields match the call arguments.

#### LaunchpadTrade

```solidity
event LaunchpadTrade(address indexed token, address indexed user, bool isBuy, uint256 amountIn, uint256 amountOut, uint256 virtualNativeReserve, uint256 virtualTokenReserve)
```

Emitted for the bonding-curve portion of a launchpad `buy` or `sell`. Not emitted for the orderbook portion after graduation; that emits `Trade`.

| Name                 | Type    | Description                           |
| -------------------- | ------- | ------------------------------------- |
| token                | address | Launchpad token.                      |
| user                 | address | Trader.                               |
| isBuy                | bool    | True for a buy.                       |
| amountIn             | uint256 | Input, including fee.                 |
| amountOut            | uint256 | Output, after fee.                    |
| virtualNativeReserve | uint256 | Curve native reserve after the trade. |
| virtualTokenReserve  | uint256 | Curve token reserve after the trade.  |

#### Mint

```solidity
event Mint(address indexed market, address indexed sender, uint256 amountQuote, uint256 amountBase)
```

Emitted by `addLiquidity`, and at launchpad graduation when the graduated supply seeds the AMM.

| Name        | Type    | Description                                    |
| ----------- | ------- | ---------------------------------------------- |
| market      | address | Market.                                        |
| sender      | address | Caller, or the Crystal contract at graduation. |
| amountQuote | uint256 | Quote added.                                   |
| amountBase  | uint256 | Base added.                                    |

#### Burn

```solidity
event Burn(address indexed market, address indexed sender, uint256 amountQuote, uint256 amountBase, address indexed to)
```

Emitted by `removeLiquidity` and `removeLiquidityETH`.

| Name        | Type    | Description     |
| ----------- | ------- | --------------- |
| market      | address | Market.         |
| sender      | address | Caller.         |
| amountQuote | uint256 | Quote returned. |
| amountBase  | uint256 | Base returned.  |
| to          | address | Recipient.      |

#### Sync

```solidity
event Sync(address indexed market, uint112 reserve0, uint112 reserve1)
```

Emitted whenever a market's AMM reserves change: after AMM fills, liquidity changes, and graduation.

| Name     | Type    | Description    |
| -------- | ------- | -------------- |
| market   | address | Market.        |
| reserve0 | uint112 | Quote reserve. |
| reserve1 | uint112 | Base reserve.  |

## Errors

| Error                                           | Meaning                                                                                                                                  |
| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `Unauthorized(address user)`                    | Caller is not permitted: not `gov`, not the order owner, not an approved forwarder, or a `userId` in options that does not match `user`. |
| `ActionFailed()`                                | A delegated market call failed, an order was not found, a balance was insufficient, or a `requireSuccess` batch action did nothing.      |
| `AlreadyRegistered()`                           | Address already has a user id, or the user-id space is exhausted.                                                                        |
| `SlippageExceeded()`                            | Output below minimum, input above maximum, or a complete-fill order could not fill within `worstPrice`.                                  |
| `Expired(uint256 timestamp)`                    | `deadline` has passed, or an expired-fee claim's deadline has not yet arrived.                                                           |
| `TransferFailed(address recipient)`             | Native ETH transfer to `recipient` failed.                                                                                               |
| `InvalidMsgValue()`                             | `msg.value` does not match the expected amount.                                                                                          |
| `InvalidParams()`                               | A parameter is out of its allowed range.                                                                                                 |
| `ZeroAddress()`                                 | Zero address passed where not allowed.                                                                                                   |
| `Overflow()`                                    | A balance or size would exceed its packed-field limit.                                                                                   |
| `NotWethMarket(address market)`                 | Native ETH supplied to a market with no WETH side.                                                                                       |
| `InvalidPath(address[] path)`                   | Path too short, or `eth` in a position the method does not allow.                                                                        |
| `InvalidMarket(address asset0, address asset1)` | No routable canonical market for the pair.                                                                                               |


# CrystalVaultFactory

Create and interact with individual vaults built on top of the Crystal protocol.

## Conventions

**Native token.** Wherever an asset address is accepted, `eth` (`0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`) means native ETH. Vaults always hold WETH; the factory wraps on deposit and unwraps on withdrawal when you pass `eth`. Vault records in `getVault` store the WETH address, not the sentinel.

**Asset order.** `deposit` and `withdraw` require `quoteAsset` and `baseAsset` to match the vault's registered pair (after `eth` normalization) or they revert with `InvalidMarket`.

**Owner-only methods.** Every method that takes a `vault` parameter and changes its state can only be called by that vault's owner, and reverts with `Unauthorized` otherwise. Governance cannot modify individual vaults; it only sets factory-wide defaults and limits.

**Shares.** Each vault's share token symbol is `CLV-` followed by the base symbol then the quote symbol, e.g. `CLV-WETHUSDC`. The vault address is the share token address.

**Metadata.**

```solidity
struct VaultMetaData {
    string name;
    string description;
    string social1;
    string social2;
    string social3;
}
```

## Vaults

#### deploy

```solidity
function deploy(address quoteAsset, address baseAsset, uint256 amountQuote, uint256 amountBase, uint256 maxShares, uint40 lockup, bool decreaseOnWithdraw, VaultMetaData metadata) external payable returns (address vault)
```

Deploys a new vault with `msg.sender` as owner and makes the initial deposit in the same call. Both initial amounts must strictly exceed the minimum deposit for their token (`minDeposit(token)` if set, otherwise `globalMinDeposit`). If either asset is `eth`, `msg.value` must equal that asset's amount. The initial deposit is made with zero slippage minimums.

**Parameters**

| Name               | Type          | Description                                                                             |
| ------------------ | ------------- | --------------------------------------------------------------------------------------- |
| quoteAsset         | address       | Quote asset, or `eth`.                                                                  |
| baseAsset          | address       | Base asset, or `eth`.                                                                   |
| amountQuote        | uint256       | Initial quote deposit. Must exceed the token's minimum.                                 |
| amountBase         | uint256       | Initial base deposit. Must exceed the token's minimum.                                  |
| maxShares          | uint256       | Cap on total shares. 0 = uncapped.                                                      |
| lockup             | uint40        | Withdrawal lockup in seconds. 0 = use the factory's `maxLockup`.                        |
| decreaseOnWithdraw | bool          | Whether the vault scales down its resting orders when a withdrawal reduces its balance. |
| metadata           | VaultMetaData | Name, description, and social links.                                                    |

**Return Values**

| Name  | Type    | Description                                                   |
| ----- | ------- | ------------------------------------------------------------- |
| vault | address | Address of the deployed vault, which is also its share token. |

#### deposit

```solidity
function deposit(address vault, address quoteAsset, address baseAsset, uint256 amountQuoteDesired, uint256 amountBaseDesired, uint256 amountQuoteMin, uint256 amountBaseMin) public payable returns (uint256 shares, uint256 amountQuote, uint256 amountBase)
```

Deposits both assets into a vault and mints shares to the caller. The factory pulls the full desired amounts, lets the vault take what it needs to keep proportions, and refunds the remainder in the same transaction. If either asset is `eth`, `msg.value` must equal that asset's desired amount; otherwise `msg.value` must be zero. Reverts if the vault is locked or closed, or if the amounts used fall below the minimums.

**Parameters**

| Name               | Type    | Description                             |
| ------------------ | ------- | --------------------------------------- |
| vault              | address | Vault address.                          |
| quoteAsset         | address | Vault's quote asset, or `eth`.          |
| baseAsset          | address | Vault's base asset, or `eth`.           |
| amountQuoteDesired | uint256 | Maximum quote to contribute.            |
| amountBaseDesired  | uint256 | Maximum base to contribute.             |
| amountQuoteMin     | uint256 | Revert if less quote than this is used. |
| amountBaseMin      | uint256 | Revert if less base than this is used.  |

**Return Values**

| Name        | Type    | Description               |
| ----------- | ------- | ------------------------- |
| shares      | uint256 | Shares minted.            |
| amountQuote | uint256 | Quote actually deposited. |
| amountBase  | uint256 | Base actually deposited.  |

#### withdraw

```solidity
function withdraw(address vault, address quoteAsset, address baseAsset, uint256 shares, uint256 amountQuoteMin, uint256 amountBaseMin) external returns (uint256 amountQuote, uint256 amountBase)
```

Burns `shares` and sends the caller their proportional quote and base. Pass `eth` for either asset to receive native ETH. Subject to the vault's lockup: shares cannot be redeemed until the caller's `unlockTimestamp` has passed. If the withdrawal leaves the owner with zero shares, the vault is automatically locked and closed.

**Parameters**

| Name           | Type    | Description                                          |
| -------------- | ------- | ---------------------------------------------------- |
| vault          | address | Vault address.                                       |
| quoteAsset     | address | Vault's quote asset, or `eth` to receive native ETH. |
| baseAsset      | address | Vault's base asset, or `eth` to receive native ETH.  |
| shares         | uint256 | Shares to redeem.                                    |
| amountQuoteMin | uint256 | Revert if less quote than this is returned.          |
| amountBaseMin  | uint256 | Revert if less base than this is returned.           |

**Return Values**

| Name        | Type    | Description     |
| ----------- | ------- | --------------- |
| amountQuote | uint256 | Quote returned. |
| amountBase  | uint256 | Base returned.  |

#### previewDeposit

```solidity
function previewDeposit(address vault, uint256 amountQuoteDesired, uint256 amountBaseDesired) external view returns (uint256 shares, uint256 amountQuote, uint256 amountBase)
```

Returns what `deposit` would mint and consume for the given desired amounts, without changing state.

**Parameters**

| Name               | Type    | Description                  |
| ------------------ | ------- | ---------------------------- |
| vault              | address | Vault address.               |
| amountQuoteDesired | uint256 | Maximum quote to contribute. |
| amountBaseDesired  | uint256 | Maximum base to contribute.  |

**Return Values**

| Name        | Type    | Description                  |
| ----------- | ------- | ---------------------------- |
| shares      | uint256 | Shares that would be minted. |
| amountQuote | uint256 | Quote that would be used.    |
| amountBase  | uint256 | Base that would be used.     |

#### previewWithdrawal

```solidity
function previewWithdrawal(address vault, uint256 shares) external view returns (uint256 amountQuote, uint256 amountBase)
```

Returns what redeeming `shares` would pay out at the vault's current balances.

**Parameters**

| Name   | Type    | Description      |
| ------ | ------- | ---------------- |
| vault  | address | Vault address.   |
| shares | uint256 | Shares to price. |

**Return Values**

| Name        | Type    | Description                   |
| ----------- | ------- | ----------------------------- |
| amountQuote | uint256 | Quote that would be returned. |
| amountBase  | uint256 | Base that would be returned.  |

#### balanceOf

```solidity
function balanceOf(address vault, address user) external view returns (uint256 shares, uint256 amountQuote, uint256 amountBase)
```

Returns a user's share balance in a vault and the quote and base those shares currently represent.

**Parameters**

| Name  | Type    | Description    |
| ----- | ------- | -------------- |
| vault | address | Vault address. |
| user  | address | Share holder.  |

**Return Values**

| Name        | Type    | Description                 |
| ----------- | ------- | --------------------------- |
| shares      | uint256 | Share balance.              |
| amountQuote | uint256 | Quote the shares represent. |
| amountBase  | uint256 | Base the shares represent.  |

## Vault Management

All methods in this section are owner-only.

#### lock

```solidity
function lock(address vault) external
```

Pauses new deposits. Withdrawals are unaffected.

**Parameters**

| Name  | Type    | Description    |
| ----- | ------- | -------------- |
| vault | address | Vault address. |

#### unlock

```solidity
function unlock(address vault) external
```

Resumes deposits.

**Parameters**

| Name  | Type    | Description    |
| ----- | ------- | -------------- |
| vault | address | Vault address. |

#### close

```solidity
function close(address vault) external returns (uint256 amountQuote, uint256 amountBase)
```

Redeems all of the owner's shares with no slippage minimums and marks the vault locked and closed. Assets are transferred as ERC-20s; the WETH side is not unwrapped. Other depositors can still withdraw from a closed vault.

**Parameters**

| Name  | Type    | Description    |
| ----- | ------- | -------------- |
| vault | address | Vault address. |

**Return Values**

| Name        | Type    | Description                  |
| ----------- | ------- | ---------------------------- |
| amountQuote | uint256 | Quote returned to the owner. |
| amountBase  | uint256 | Base returned to the owner.  |

#### changeMaxShares

```solidity
function changeMaxShares(address vault, uint256 newMaxShares) external
```

Sets the cap on total shares.

**Parameters**

| Name         | Type    | Description            |
| ------------ | ------- | ---------------------- |
| vault        | address | Vault address.         |
| newMaxShares | uint256 | New cap. 0 = uncapped. |

#### changeLockup

```solidity
function changeLockup(address vault, uint40 newLockup) external
```

Sets the withdrawal lockup. The vault contract validates the value against the factory's `maxLockup`.

**Parameters**

| Name      | Type    | Description        |
| --------- | ------- | ------------------ |
| vault     | address | Vault address.     |
| newLockup | uint40  | Lockup in seconds. |

#### changeDecreaseOnWithdraw

```solidity
function changeDecreaseOnWithdraw(address vault, bool newDecrease) external
```

Toggles whether the vault scales down its resting orders when a withdrawal reduces its balance.

**Parameters**

| Name        | Type    | Description    |
| ----------- | ------- | -------------- |
| vault       | address | Vault address. |
| newDecrease | bool    | New setting.   |

#### changeOrderCap

```solidity
function changeOrderCap(address vault, uint16 newCap) external
```

Sets the maximum number of open orders the vault may hold. The vault contract validates the value against the factory's `maxOrderCap`.

**Parameters**

| Name   | Type    | Description    |
| ------ | ------- | -------------- |
| vault  | address | Vault address. |
| newCap | uint16  | New order cap. |

#### changeMarket

```solidity
function changeMarket(address vault) external
```

Re-reads the canonical market for the vault's pair from Crystal. Call this if the canonical market for the pair has changed since the vault was deployed.

**Parameters**

| Name  | Type    | Description    |
| ----- | ------- | -------------- |
| vault | address | Vault address. |

#### claimFees

```solidity
function claimFees(address vault, address[] tokens) external
```

Claims the vault's claimable rewards on Crystal for the listed tokens to the owner. Vaults do not create markets and are unlikely to be used as referrers, so these are typically incentives distributed by the protocol or third parties.

**Parameters**

| Name   | Type       | Description      |
| ------ | ---------- | ---------------- |
| vault  | address    | Vault address.   |
| tokens | address\[] | Tokens to claim. |

#### clearCloidSlots

```solidity
function clearCloidSlots(address vault, uint256 userId, uint256[] ids) external
```

Calls Crystal's `clearCloidSlots` on behalf of the vault.

**Parameters**

| Name   | Type       | Description                    |
| ------ | ---------- | ------------------------------ |
| vault  | address    | Vault address.                 |
| userId | uint256    | The vault's Crystal user id.   |
| ids    | uint256\[] | Cloid slots to clear (1–1023). |

## Governance

Callable only by `gov`. These set factory-wide defaults and limits; they do not change existing vaults.

#### changeGov

```solidity
function changeGov(address newGov) external
```

Transfers governance.

**Parameters**

| Name   | Type    | Description             |
| ------ | ------- | ----------------------- |
| newGov | address | New governance address. |

#### changeMaxOrderCap

```solidity
function changeMaxOrderCap(uint16 newCap) external
```

Sets the maximum order cap a vault may configure.

**Parameters**

| Name   | Type   | Description                      |
| ------ | ------ | -------------------------------- |
| newCap | uint16 | New maximum. Must be below 1024. |

#### changeMaxLockup

```solidity
function changeMaxLockup(uint40 newLockup) external
```

Sets the maximum lockup a vault may configure. Also the default lockup for vaults deployed with `lockup = 0`.

**Parameters**

| Name      | Type   | Description                                          |
| --------- | ------ | ---------------------------------------------------- |
| newLockup | uint40 | New maximum in seconds. At most 30 days (2,592,000). |

#### changeGlobalMinDeposit

```solidity
function changeGlobalMinDeposit(uint256 newGlobalMinDeposit) external
```

Sets the default minimum initial deposit for tokens with no per-token override.

**Parameters**

| Name                | Type    | Description                                            |
| ------------------- | ------- | ------------------------------------------------------ |
| newGlobalMinDeposit | uint256 | New minimum. Initial deposits must strictly exceed it. |

#### changeMinDeposit

```solidity
function changeMinDeposit(address token, uint256 newMinDeposit) external
```

Sets a per-token minimum initial deposit. Passing `eth` sets the value for WETH.

**Parameters**

| Name          | Type    | Description                                      |
| ------------- | ------- | ------------------------------------------------ |
| token         | address | Token, or `eth` for WETH.                        |
| newMinDeposit | uint256 | New minimum. 0 falls back to `globalMinDeposit`. |

## State and Registry Getters

| Name             | Signature                                                                                                                | Description                                                                                 |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- |
| weth             | `weth() → address`                                                                                                       | Wrapped native token.                                                                       |
| eth              | `eth() → address`                                                                                                        | Native-token sentinel.                                                                      |
| gov              | `gov() → address`                                                                                                        | Governance address.                                                                         |
| crystal          | `crystal() → address`                                                                                                    | Crystal core contract.                                                                      |
| allVaults        | `allVaults(uint256) → address`                                                                                           | Vault at index.                                                                             |
| allVaultsLength  | `allVaultsLength() → uint256`                                                                                            | Number of deployed vaults.                                                                  |
| getVault         | `getVault(address) → (quoteAsset, baseAsset, owner, totalShares, maxShares, lockup, decreaseOnWithdraw, locked, closed)` | Vault record. `totalShares` is updated on every deposit and withdrawal through the factory. |
| minDeposit       | `minDeposit(address token) → uint256`                                                                                    | Per-token minimum initial deposit; 0 if none.                                               |
| globalMinDeposit | `globalMinDeposit() → uint256`                                                                                           | Default minimum initial deposit.                                                            |
| maxOrderCap      | `maxOrderCap() → uint16`                                                                                                 | Maximum order cap a vault may set.                                                          |
| maxLockup        | `maxLockup() → uint40`                                                                                                   | Maximum lockup a vault may set, and the default for new vaults.                             |

***

## Events

Events and errors emitted by CrystalVaultFactory.

#### VaultDeployed

```solidity
event VaultDeployed(address indexed vault, address quoteAsset, address baseAsset, address owner, uint256 maxShares, uint256 lockup, bool decreaseOnWithdraw, VaultMetaData metadata)
```

Emitted by `deploy`, before the initial `Deposit`.

| Name               | Type          | Description                                                                  |
| ------------------ | ------------- | ---------------------------------------------------------------------------- |
| vault              | address       | New vault address.                                                           |
| quoteAsset         | address       | Quote asset (WETH, not `eth`).                                               |
| baseAsset          | address       | Base asset (WETH, not `eth`).                                                |
| owner              | address       | Vault owner.                                                                 |
| maxShares          | uint256       | Share cap. 0 = uncapped.                                                     |
| lockup             | uint256       | Effective lockup in seconds. Reports `maxLockup` when the deployer passed 0. |
| decreaseOnWithdraw | bool          | Decrease-on-withdraw setting.                                                |
| metadata           | VaultMetaData | Name, description, and social links.                                         |

#### Deposit

```solidity
event Deposit(address indexed vault, address indexed sender, uint256 shares, uint256 quoteAmount, uint256 baseAmount)
```

Emitted by `deposit`, including the initial deposit in `deploy`.

| Name        | Type    | Description      |
| ----------- | ------- | ---------------- |
| vault       | address | Vault.           |
| sender      | address | Depositor.       |
| shares      | uint256 | Shares minted.   |
| quoteAmount | uint256 | Quote deposited. |
| baseAmount  | uint256 | Base deposited.  |

#### Withdraw

```solidity
event Withdraw(address indexed vault, address indexed sender, uint256 shares, uint256 quoteAmount, uint256 baseAmount)
```

Emitted by `withdraw` and `close`.

| Name        | Type    | Description        |
| ----------- | ------- | ------------------ |
| vault       | address | Vault.             |
| sender      | address | Redeeming account. |
| shares      | uint256 | Shares burned.     |
| quoteAmount | uint256 | Quote returned.    |
| baseAmount  | uint256 | Base returned.     |

#### MaxSharesChanged

```solidity
event MaxSharesChanged(address indexed vault, uint256 maxShares)
```

Emitted by `changeMaxShares`.

| Name      | Type    | Description |
| --------- | ------- | ----------- |
| vault     | address | Vault.      |
| maxShares | uint256 | New cap.    |

#### LockupChanged

```solidity
event LockupChanged(address indexed vault, uint256 lockup)
```

Emitted by `changeLockup`.

| Name   | Type    | Description            |
| ------ | ------- | ---------------------- |
| vault  | address | Vault.                 |
| lockup | uint256 | New lockup in seconds. |

#### DecreaseOnWithdrawChanged

```solidity
event DecreaseOnWithdrawChanged(address indexed vault, bool newDecrease)
```

Emitted by `changeDecreaseOnWithdraw`.

| Name        | Type    | Description  |
| ----------- | ------- | ------------ |
| vault       | address | Vault.       |
| newDecrease | bool    | New setting. |

#### Locked

```solidity
event Locked(address indexed vault)
```

Emitted by `lock`, and automatically when a `withdraw` or `close` leaves the owner with zero shares.

| Name  | Type    | Description |
| ----- | ------- | ----------- |
| vault | address | Vault.      |

#### Unlocked

```solidity
event Unlocked(address indexed vault)
```

Emitted by `unlock`.

| Name  | Type    | Description |
| ----- | ------- | ----------- |
| vault | address | Vault.      |

#### Closed

```solidity
event Closed(address indexed vault)
```

Emitted when a vault is closed, either by `close` or automatically when a `withdraw` leaves the owner with zero shares. A vault cannot be reopened.

| Name  | Type    | Description |
| ----- | ------- | ----------- |
| vault | address | Vault.      |

## Errors

| Error                                           | Meaning                                                                               |
| ----------------------------------------------- | ------------------------------------------------------------------------------------- |
| `SizeBelowMin()`                                | An initial deposit in `deploy` did not exceed the token's minimum.                    |
| `Unauthorized(address user)`                    | Caller is not the vault owner (owner-only methods) or not `gov` (governance methods). |
| `InvalidMarket(address asset0, address asset1)` | `quoteAsset`/`baseAsset` do not match the vault's pair.                               |
| `InvalidMsgValue()`                             | `msg.value` does not match the `eth` amount, or was sent when no asset is `eth`.      |
| `InvalidParams()`                               | Governance parameter out of range.                                                    |
| `TransferFailed(address recipient)`             | Native ETH refund or payout failed.                                                   |

`changeOrderCap` and `changeMarket` do not emit factory events; the vault contract handles those changes directly.


# Contract Addresses

Interact with live deployments of the Crystal protocol on public blockchains.

### Monad Mainnet:

Crystal: `0x508254c838B2e936B0631440c5C6E3AB3a4a98BD`

CrystalVaultFactory: `0x49B637F200E52F56Fd9E081B6ee29c0F8D096ffc`

For the address of an individual market, query the `getMarket` function on the router or see the [Deployed Market Parameters](/developer-resources/deployed-market-parameters) page.


# Deployed Market Parameters

The taker fee for all volatile markets is set at 0.035%, while the taker fee for stable markets are currently set at 0.01%. The minimum limit order size is set at 5 USDC or 100 MON. All markets are currently deployed on Monad Mainnet.

<table data-full-width="true"><thead><tr><th width="147">Market</th><th width="191.666748046875">Address</th><th width="174">Quote Address</th><th width="189.3333740234375">Base Address</th><th width="94">Scale Factor</th><th>Max Price</th></tr></thead><tbody><tr><td>MON/USDC</td><td><code>0x39fAE95717cfD4bdA22317F1c124660A166b6BEc</code></td><td><code>0x754704Bc059F8C67012fEd69BC8A327a5aafb603</code></td><td><code>0x3bd359C1119dA7Da1D913D1C4D2B7c461115433A</code></td><td>10**21</td><td>1,000,000 USDC, base-10 ticks with 5 digits of precision</td></tr><tr><td>cbBTC/USDC</td><td>TBD</td><td><code>0x754704Bc059F8C67012fEd69BC8A327a5aafb603</code></td><td><code>0xd18B7EC58Cdf4876f6AFebd3Ed1730e4Ce10414b</code></td><td>10**3</td><td>1,000,000 USDC, increments of 0.1</td></tr><tr><td>AUSD/USDC</td><td>TBD</td><td><code>0x754704Bc059F8C67012fEd69BC8A327a5aafb603</code></td><td><code>0x00000000eFE302BEAA2b3e6e1b18d08D69a9012a</code></td><td>10**4</td><td>10 USDC, increments of 0.0001</td></tr><tr><td>XAUt0/USDC</td><td>TBD</td><td><code>0x754704Bc059F8C67012fEd69BC8A327a5aafb603</code></td><td><code>0x01bFF41798a0BcF287b996046Ca68b395DbC1071</code></td><td>10**9</td><td>1,000,000 USDC, base-10 ticks with 5 digits of precision</td></tr></tbody></table>


