# Tracking Price

| Parameter   | Description                                | Format                            |
| ----------- | ------------------------------------------ | --------------------------------- |
| `price` (P) | Center/reference price of tokenX in tokenY | 1e24 precision                    |
| `alpha`     | Concentration factor                       | BPS (10000 = 1.0)                 |
| `feeHbps`   | Trading fee                                | Hundred basis points (1e6 = 100%) |
| `expiry`    | When the data expires                      | Unix timestamp                    |

This guide explains how pricing works in OraclePool and how to track price changes. The key insight is that the execution price is **not** simply the oracle price — it's computed from both oracle data AND pool balances.

### The Pricing Model

OraclePool uses a **Concentrated Liquidity with Asymmetric Pricing (CLAP)** model (an extension of the simpler CLCP curve). The price is determined by two components.

#### Oracle Data (Set by Operator)

The oracle operator sets these parameters:

| Parameter   | Description                                | Format                                         |
| ----------- | ------------------------------------------ | ---------------------------------------------- |
| `price` (P) | Center/reference price of tokenX in tokenY | 1e24 precision                                 |
| `alpha`     | Concentration factor                       | BPS (10000 = 1.0). Valid range: (10000, 65535] |
| `feeHbps`   | Trading fee                                | Hundredths of a basis point (1e6 = 100%)       |
| `expiry`    | When the data expires (exclusive)          | Unix timestamp (40-bit)                        |

The `alpha` parameter defines the price range: **\[P/alpha, P\*alpha]**

For example, with `alpha = 10100` (1.01 in BPS):

* If oracle price P = 1.0, the actual price can range from \~0.99 to \~1.01

#### Pool Balances

The pool's `tokenX` / `tokenY` balances determine where within the price range the actual price sits. There is a single live quantity per token — the pool does **not** distinguish "reserves designated for trading" from "balances held"; everything held by the pool participates in pricing.

CLAP splits each swap into two phases:

1. **Stable phase.** If the trade brings the pool closer to 50/50 value (P · X ≈ Y), the improving portion executes at the flat oracle price P.
2. **Curve phase.** Any remaining input swaps along the concentrated-liquidity x·y = k curve using virtual reserves derived from real balances plus α.

Worsening trades skip the stable phase and execute entirely on the curve.

{% hint style="info" %}
Oracle price alone is NOT the execution price. To compute the true price for a given trade size, use `getQuote(...)` on the pool (or call the oracle's `getQuote(...)` directly with the current balances). For a marginal bid/ask spread without an amount, use `getCurrentPrice(key, balanceX, balanceY)`.
{% endhint %}

### Reading Oracle Data

#### Computing the Oracle Key

Oracle data is stored by a key derived from the token pair:

```solidity
bytes32 key = keccak256(abi.encodePacked(tokenX, tokenY));
```

{% hint style="warning" %}
Order matters! `tokenX` must come first when computing the key.
{% endhint %}

#### Getting the Data

```solidity
// On the oracle contract (get oracle address from pool.getOracle())
function getData(bytes32 key)
    external view
    returns (uint256 price, uint256 feeHbps, uint256 alpha, uint256 expiry);
```

Note: `getData` does **not** check expiry. To get just the price with expiry enforcement, call `getPrice(key)` instead.

Example usage:

```solidity
address oracle = pool.getOracle();
(address tokenX, address tokenY) = pool.getTokens();

bytes32 key = keccak256(abi.encodePacked(tokenX, tokenY));
(uint256 price, uint256 feeHbps, uint256 alpha, uint256 expiry) = IClapOracle(oracle).getData(key);
```

### Getting the Current Price

The deployed oracle is `ClapOracle`. It exposes a **bid/ask spread** rather than a single mid price:

```solidity
function getCurrentPrice(bytes32 key, uint256 reserveX, uint256 reserveY)
    external view
    returns (uint256 bidPriceE24, uint256 askPriceE24);
```

The oracle price P is always one of the two bounds; the other is the curve-implied price derived from the current pool balances. Both are 1e24-scaled.

{% stepper %}
{% step %}

#### How getCurrentPrice works — step 1

Reads and validates (non-expired) the oracle data for `key` (price P, alpha).
{% endstep %}

{% step %}

#### How getCurrentPrice works — step 2

Uses the provided balances as `reserveX` / `reserveY`.
{% endstep %}

{% step %}

#### How getCurrentPrice works — step 3

Computes the curve-implied price from the balances and pairs it with P. The smaller of the two becomes the bid; the larger becomes the ask.
{% endstep %}

{% step %}

#### How getCurrentPrice works — step 4

Returns `(bidPriceE24, askPriceE24)` with 1e24 precision.
{% endstep %}
{% endstepper %}

Example:

```solidity
address oracle = pool.getOracle();
(address tokenX, address tokenY) = pool.getTokens();
(uint256 balanceX, uint256 balanceY) = pool.getBalances();

bytes32 key = keccak256(abi.encodePacked(tokenX, tokenY));
(uint256 bidPriceE24, uint256 askPriceE24) =
    IClapOracle(oracle).getCurrentPrice(key, balanceX, balanceY);

// Prices are tokenX denominated in tokenY with 1e24 precision
// humanPrice = priceE24 / 1e24 * 10^(decimalsX - decimalsY)
```

For an executable quote on a specific trade size (which includes the CLAP stable-phase mechanics and fees), use `pool.getQuote(swapXtoY, amountIn)` — see the Making a Swap page.

### Price Format and Decimal Adjustment

The price returned is with **1e24 precision** and represents the price of tokenX in terms of tokenY.

To get a human-readable price, adjust for token decimals:

```
humanPrice = priceE24 / 1e24 * 10^(decimalsX - decimalsY)
```

Example — tokenX has 18 decimals and tokenY (USDC) has 6 decimals:

```solidity
// getCurrentPrice returns: 18386714238 (1.838e10)
// decimalsX = 18, decimalsY = 6

// humanPrice = 1.838e10 / 1e24 * 10^(18-6)
//            = 1.838e10 / 1e24 * 1e12
//            = 1.838e10 / 1e12
//            = 0.01838
// This means 1 tokenX = 0.01838 USDC
```

Another example — tokenX has 6 decimals and tokenY has 18 decimals:

```solidity
// Suppose getCurrentPrice returns: 5e34
// decimalsX = 6, decimalsY = 18

// humanPrice = 5e34 / 1e24 * 10^(6-18)
//            = 5e10 * 1e-12
//            = 0.05
// This means 1 tokenX = 0.05 tokenY
```

### Tracking Price Changes

Price can change from two sources:

#### Balance Changes (Events Available)

Pool balances change on swaps, deposits and withdrawals:

```solidity
event Swap(
    address indexed sender,
    address indexed recipient,
    bool indexed swapXtoY,
    uint256 actualAmountIn,
    uint256 amountOut,
    uint256 feeIn,
    uint256 feeOut
);

event Deposited(
    address indexed user,
    address indexed receiver,
    uint256 amountX,
    uint256 amountY,
    uint256 shares
);

event Withdrawn(
    address indexed user,
    address indexed receiver,
    uint256 shares,
    uint256 amountX,
    uint256 amountY
);
```

Notes:

* `actualAmountIn` is the gross input including fee.
* Only one of `feeIn` / `feeOut` is non-zero per swap: `feeIn` is in tokenIn for Y→X swaps; `feeOut` is in tokenOut (tokenY) for X→Y swaps. Both are forwarded to the FeeRewarder when set.

#### Oracle Data Changes (No Events)

{% hint style="danger" %}
The oracle's `setData()` function does NOT emit events. Its signature is parameter-less — the price/fee/alpha/expiry payload is read directly from calldata (see `src/libraries/Oracle.sol` in the repo for the layout). To track oracle changes you must either watch transactions calling `setData()` on the oracle contract and decode the raw calldata, or periodically poll `getData(key)`.
{% endhint %}

### TypeScript Example

{% code title="PriceTracker.ts" %}

```typescript
import { ethers } from 'ethers';

const ORACLE_POOL_ABI = [
    "function getOracle() view returns (address)",
    "function getTokens() view returns (address tokenX, address tokenY)",
    "function getBalances() view returns (uint256 totalX, uint256 totalY)",
    "event Swap(address indexed sender, address indexed recipient, bool indexed swapXtoY, uint256 actualAmountIn, uint256 amountOut, uint256 feeIn, uint256 feeOut)",
    "event Deposited(address indexed user, address indexed receiver, uint256 amountX, uint256 amountY, uint256 shares)",
    "event Withdrawn(address indexed user, address indexed receiver, uint256 shares, uint256 amountX, uint256 amountY)"
];

const CLAP_ORACLE_ABI = [
    "function getData(bytes32 key) view returns (uint256 price, uint256 feeHbps, uint256 alpha, uint256 expiry)",
    "function getCurrentPrice(bytes32 key, uint256 reserveX, uint256 reserveY) view returns (uint256 bidPriceE24, uint256 askPriceE24)"
];

const ERC20_ABI = [
    "function decimals() view returns (uint8)"
];

class PriceTracker {
    private pool: ethers.Contract;
    private oracle: ethers.Contract;
    private key: string;
    private decimalsX: number;
    private decimalsY: number;

    constructor(
        provider: ethers.Provider,
        poolAddress: string
    ) {
        this.pool = new ethers.Contract(poolAddress, ORACLE_POOL_ABI, provider);
    }

    async initialize() {
        // Get oracle and tokens
        const [oracleAddress, tokens] = await Promise.all([
            this.pool.getOracle(),
            this.pool.getTokens()
        ]);
        const [tokenX, tokenY] = [tokens.tokenX, tokens.tokenY];

        this.oracle = new ethers.Contract(
            oracleAddress,
            CLAP_ORACLE_ABI,
            this.pool.runner
        );

        // Compute key
        this.key = ethers.solidityPackedKeccak256(
            ['address', 'address'],
            [tokenX, tokenY]
        );

        // Get decimals for price formatting (convert BigInt to number)
        const tokenXContract = new ethers.Contract(tokenX, ERC20_ABI, this.pool.runner);
        const tokenYContract = new ethers.Contract(tokenY, ERC20_ABI, this.pool.runner);

        const [decX, decY] = await Promise.all([
            tokenXContract.decimals(),
            tokenYContract.decimals()
        ]);
        this.decimalsX = Number(decX);
        this.decimalsY = Number(decY);

        console.log(`Initialized tracker for ${tokenX}/${tokenY}`);
        console.log(`Oracle key: ${this.key}`);
    }

    async getCurrentPrice(): Promise<{
        bidPriceE24: bigint;
        askPriceE24: bigint;
        humanBid: number;
        humanAsk: number;
    }> {
        const { totalX, totalY }: { totalX: bigint; totalY: bigint } =
            await this.pool.getBalances();
        const [bidPriceE24, askPriceE24]: [bigint, bigint] =
            await this.oracle.getCurrentPrice(this.key, totalX, totalY);

        const decimalAdjustment = this.decimalsX - this.decimalsY;
        const toHuman = (p: bigint) =>
            parseFloat(p.toString()) / 1e24 * Math.pow(10, decimalAdjustment);

        return {
            bidPriceE24,
            askPriceE24,
            humanBid: toHuman(bidPriceE24),
            humanAsk: toHuman(askPriceE24),
        };
    }

    async getOracleData() {
        const [price, feeHbps, alpha, expiry]: [bigint, bigint, bigint, bigint] =
            await this.oracle.getData(this.key);
        return {
            price,
            feeHbps,
            alpha,
            expiry,
            expiryDate: new Date(parseInt(expiry.toString()) * 1000)
        };
    }
}

// Usage
async function main() {
    const provider = new ethers.JsonRpcProvider("https://rpc3.monad.xyz");

    const tracker = new PriceTracker(provider, "0x...poolAddress");
    await tracker.initialize();

    const { bidPriceE24, askPriceE24, humanBid, humanAsk } = await tracker.getCurrentPrice();
    console.log(`Bid / Ask (1e24): ${bidPriceE24} / ${askPriceE24}`);
    console.log(`Bid / Ask (human): ${humanBid} / ${humanAsk}`);

    const oracleData = await tracker.getOracleData();
    console.log('Oracle data:', oracleData);
}
```

{% endcode %}

### Python Example

{% code title="price:tracker.py" %}

```python
from web3 import Web3

def compute_oracle_key(token_x: str, token_y: str) -> bytes:
    """Compute the oracle data key for a token pair."""
    packed = bytes.fromhex(token_x[2:]) + bytes.fromhex(token_y[2:])
    return Web3.keccak(packed)

def get_current_price(w3: Web3, pool_address: str) -> dict:
    """Get the current bid/ask spread from an OraclePool."""

    pool_abi = [
        {"name": "getOracle", "type": "function", "inputs": [], "outputs": [{"type": "address"}]},
        {"name": "getTokens", "type": "function", "inputs": [],
         "outputs": [{"name": "tokenX", "type": "address"}, {"name": "tokenY", "type": "address"}]},
        {"name": "getBalances", "type": "function", "inputs": [],
         "outputs": [{"name": "totalX", "type": "uint256"}, {"name": "totalY", "type": "uint256"}]},
    ]

    oracle_abi = [
        {"name": "getData", "type": "function", "inputs": [{"type": "bytes32"}],
         "outputs": [{"type": "uint256"}, {"type": "uint256"}, {"type": "uint256"}, {"type": "uint256"}]},
        {"name": "getCurrentPrice", "type": "function",
         "inputs": [{"type": "bytes32"}, {"type": "uint256"}, {"type": "uint256"}],
         "outputs": [{"name": "bidPriceE24", "type": "uint256"}, {"name": "askPriceE24", "type": "uint256"}]},
    ]

    pool = w3.eth.contract(address=pool_address, abi=pool_abi)

    # Get pool info
    oracle_address = pool.functions.getOracle().call()
    token_x, token_y = pool.functions.getTokens().call()
    balance_x, balance_y = pool.functions.getBalances().call()

    # Compute key and get price
    oracle = w3.eth.contract(address=oracle_address, abi=oracle_abi)
    key = compute_oracle_key(token_x, token_y)

    bid_price_e24, ask_price_e24 = oracle.functions.getCurrentPrice(key, balance_x, balance_y).call()
    oracle_data = oracle.functions.getData(key).call()

    return {
        "token_x": token_x,
        "token_y": token_y,
        "balance_x": balance_x,
        "balance_y": balance_y,
        "bid_price_e24": bid_price_e24,
        "ask_price_e24": ask_price_e24,
        "oracle_price": oracle_data[0],
        "fee_hbps": oracle_data[1],
        "alpha": oracle_data[2],
        "expiry": oracle_data[3],
    }

# Usage
if __name__ == "__main__":
    w3 = Web3(Web3.HTTPProvider("https://rpc3.monad.xyz"))

    result = get_current_price(w3, "0x...poolAddress")

    print(f"Token X: {result['token_x']}")
    print(f"Token Y: {result['token_y']}")
    print(f"Balances: {result['balance_x']} / {result['balance_y']}")
    print(f"Bid / Ask (1e24): {result['bid_price_e24']} / {result['ask_price_e24']}")
    print(f"Oracle Price (1e24): {result['oracle_price']}")
    print(f"Alpha: {result['alpha']} BPS")
```

{% endcode %}

### Key Takeaways

{% stepper %}
{% step %}

#### Price is a function of oracle data and pool balances

You need both to get the actual price. For trade-size-specific quotes use `getQuote`; for the marginal bid/ask spread use `getCurrentPrice`.
{% endstep %}

{% step %}

#### Oracle data has no events

`setData()` takes no parameters — the payload lives in the raw calldata. You must decode that calldata or periodically poll `getData(key)`.
{% endstep %}

{% step %}

#### Balance changes have events

Watch `Swap`, `Deposited` and `Withdrawn` for balance-driven price changes.
{% endstep %}

{% step %}

#### Price precision is 1e24

Use `humanPrice = priceE24 / 1e24 * 10^(decimalsX - decimalsY)`
{% endstep %}

{% step %}

#### Alpha defines the allowed range

Actual price is bounded by \[P/alpha, P\*alpha]. Alpha is in BPS where 10000 = 1.0; the valid on-chain range is (10000, 65535].
{% endstep %}
{% endstepper %}

***

## Agent Instructions: Querying This Documentation

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

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

```
GET https://developers.lfj.gg/poe/tracking-price.md?ask=<question>
```

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

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


---

# Agent Instructions: Querying This Documentation

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

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

```
GET https://developers.lfj.gg/poe/tracking-price.md?ask=<question>
```

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

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