> For the complete documentation index, see [llms.txt](https://docs.nekodex.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.nekodex.org/nekodex-playground/docs-for-devs/contracts/maker/oracle-maker.md).

# Oracle Maker

{% hint style="danger" %}
Liquidity provision is whitelisted until initial beta testing has completed.
{% endhint %}

## Overview

Oracle maker uses price feeds from Pyth and may employ feeds from Chainlink and other providers as they become available/needed.&#x20;

* Each market backed by an oracle maker has a discrete liquidity pool.&#x20;
* Market orders with oracle pools are fill-or-kill, ie. no partial fills unless this logic is handled at a higher layer in the stack (e.g. by the [Order Gateway](/nekodex-playground/docs-for-devs/contracts/order-gateway.md) for limit orders filled by oracle maker pools).
* All orders sent to an oracle maker pool must include a delay for front running protection - this can be handled by the order gateway.&#x20;

### Filling taker orders

<figure><img src="/files/8i6BzNMJ57yZt5a0VT6U" alt=""><figcaption></figcaption></figure>

#### Order filled event

{% code overflow="wrap" %}

```solidity
/// @notice Emitted when an order is filled by a Pyth Oracle Maker. 
///         It reveals all information associated with the trade price.
event PythOracleOrderFilled(
    uint256 orcalePrice,    // In quote asset as wei
                            // Assumes price >= 0
    uint256 tradePrice,     // In quote asset as wei
                            // Assumes price >= 0
    uint256 size,           // In base asset as wei
    uint256 fee,            // In quote asset as wei
    uint256 spread          // In percentage (1e6 = 100%)
);
```

{% endcode %}

### Deposit liquidity

Deposits are managed via the [Vault](/nekodex-playground/docs-for-devs/contracts/vault.md) contract. Only collateral supported by the vault can be used by makers (LPs).

<figure><img src="/files/K8K1wZX88QZOFm1oteFe" alt=""><figcaption></figcaption></figure>

### Withdraw liquidity

LP holds a share of the global Maker account value, and can withdraw according to the value of their share.

* Example:
  * Maker account value = 100 USDC
    * `accountValue = margin + positionSize * price + openNotional`
    * `price = abs(fillOrder(positionSize) / positionSize)`
  * LP withdraw 10% of liquidity
    * `withdraw(share)`
    * `sharePrice = accountValue / totalSupply()`
  * LP gets 10 USDC (`share * sharePrice`)

## Pricing

### Quote Token

* At launch, all prices are in USD

### Dynamic Premium (spread calculation)

A spread is added to trades to offset makers' risk exposure. Risk exposure potentially includes:

* Long/short skew
* Entry price
* Funding (however, impacts should be minimal because the maker aims to have zero positions over a long period of time)

Risk mitigation:

* Minimize inventory risk (e.g. target position size = 0)
* One-sided circuit breaker when maximum risk exposure is reached (stop taking more long or short, but accept trades that reduce exposure)

Dynamic premium is modeled on a simplified Avellaneda-Stoikov model.

* Givens
  * `midPrice = oraclePrice`: Traditionally in a CLOB, `midPrice` is the midpoint of max bid & min ask; in `PythOracleMaker` we let `oraclePrice` assume this role because you can think of it as the center of bid & ask when spread = 0.
  * `reservationPrice`: Risk-adjusted price for market making. It is a function of `midPrice` (`oraclePrice`), the maker’s risk exposure, and the configurables below. The premium is the difference between `reservationPrice` and `midPrice`
    * `reservationPrice = midPrice * (1 - maxSpread * makerPosition / maxAbsPosition)`
  * `bid = min(midPrice, reservationPrice)`
  * `ask = max(midPrice, reservationPrice)`
* Configurables
  * `maxSpread`: Sensitivity of the maker’s price premium to its risk exposure. This is modeled as the maximum price difference allowed between `midPrice` and `reservationPrice`. The larger `maxSpread` is, the faster the maker increases the premium in response to higher risk exposure.
  * `maxAbsPosition`: Position size cap where the maker would stop taking more positions. We also use it to calculate `reservationPrice`. The max position is a safety threshold and should be configured so that it is not reached too often, because once it is reached, the maker would stop providing liquidity on one side.

## Contracts

{% hint style="info" %}
See [Order Gateway](/nekodex-playground/docs-for-devs/contracts/order-gateway.md)for more details about interacting with oracle maker pools.
{% endhint %}

### OracleMaker.sol

#### Event

{% code overflow="wrap" %}

```solidity
event Deposited(
    address depositor,
    uint256 shares, // Amount of shares minted
    uint256 underlying // Amount of underlying token deposited
);
```

{% endcode %}

{% code overflow="wrap" %}

```solidity
event Withdrawn(
    address withdrawer,
    uint256 shares, // Amount of shares burned
    uint256 underlying // Amount of underlying tokens withdrawn
);
```

{% endcode %}

{% code overflow="wrap" %}

```solidity
/// @notice Emitted when an order is filled by a Pyth Oracle Maker.
///         It reveals information associated with the trade price.
event OMOrderFilled(
    uint256 marketId,
    uint256 oraclePrice, // In quote asset as wei, assume price >= 0
    int256 baseAmount, // Base token amount filled (from taker's perspective)
    int256 quoteAmount // Quote token amount filled (from taker's perspective)
);
```

{% endcode %}

#### Public functions (write)

{% code overflow="wrap" %}

```solidity
/// @notice Function is currently whitelisted.
///         Whitelist will be removed at a future date.
function deposit(uint256 amountXCD) external onlyWhitelistLp returns (uint256) {
        address depositor = _sender();
        address maker = address(this);

...
```

{% endcode %}

{% code overflow="wrap" %}

```solidity
/// @notice Function is currently whitelisted.
///         Whitelist will be removed at a future date.
function withdraw(uint256 shares) external onlyWhitelistLp returns (uint256) {
        address withdrawer = _sender();

...
```

{% endcode %}

{% code overflow="wrap" %}

```solidity
/// @notice Function should be called via the order gateway
function fillOrder(
        bool isBaseToQuote,
        bool isExactInput,
        uint256 amount,
        bytes calldata
    ) external onlyClearingHouse returns (uint256, bytes memory) {
        uint256 basePrice = _getPrice();
        uint256 basePriceWithSpread = _getBasePriceWithSpread(basePrice, isBaseToQuote);

        // - `amount` base -> `amount * basePrice` quote
        //   (isBaseToQuote=true, isExactInput=true, openNotional = `amount * basePrice`)
        // - `amount` base <- `amount * basePrice` quote
        //   (isBaseToQuote=false, isExactInput=false, openNotional = -`amount * basePrice`)
        // - `amount / basePrice` base -> `amount` quote
        //   (isBaseToQuote=true, isExactInput=false, openNotional = `amount`)
        // - `amount / basePrice` base <- `amount` quote
        //   (isBaseToQuote=false, isExactInput=true, openNotional = -`amount`)

        int256 baseAmount;
        int256 quoteAmount;
        uint256 oppositeAmount;

...
```

{% endcode %}

#### Public functions (read)

{% code overflow="wrap" %}

```solidity
/// @notice Read current pool utilization ratio
function getUtilRatio() external view returns (uint256, uint256) {
        if (totalSupply() == 0) {
            return (0, 0);
        }
    
        IVault vault = _getVault();
        int256 positionSize = vault.getPositionSize(_getOracleMakerStorage().marketId, address(this));
    
        if (positionSize == 0) {
            return (0, 0);
        }
    
        uint256 price = _getPrice();
        int256 positionRate = _getPositionRate(price);
        // position rate > 0, maker has long position, set long util ratio to 0 so taker tends to long
        // position rate < 0, maker has short position, set short util ratio to 0 so taker tends to short
        return positionRate > 0 ? (uint256(0), positionRate.toUint256()) : ((-positionRate).toUint256(), uint256(0));
    }

/// @param
function getUtilRatio() external view returns (
        uint256 longUtilRatio, 
        uint256 shortUtilRatio
    );
```

{% endcode %}

## Numeric Representation

* Pyth uses [fixed-point numeric representation](https://docs.pyth.network/documentation/pythnet-price-feeds/best-practices#fixed-point-numeric-representation).
* `PythOracleMaker` will translate Pyth price into its own native numeric representation: `wei`
* Assumes `oraclePrice >= 0`

## Run-time Dependencies

* [Pyth EVM contract addresses](https://docs.pyth.network/price-feeds/contract-addresses/evm)
