Appearance
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 | Hundredths of a basis point (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:
- 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.
- 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.
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).
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));WARNING
Order matters! tokenX must come first when computing the key.
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.
How getCurrentPrice works — step 1
Reads and validates (non-expired) the oracle data for key (price P, alpha).
How getCurrentPrice works — step 2
Uses the provided balances as reserveX / reserveY.
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.
How getCurrentPrice works — step 4
Returns (bidPriceE24, askPriceE24) with 1e24 precision.
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 USDCAnother 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 tokenYTracking 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:
actualAmountInis the gross input including fee.- Only one of
feeIn/feeOutis non-zero per swap:feeInis in tokenIn for Y→X swaps;feeOutis in tokenOut (tokenY) for X→Y swaps. Both are forwarded to the FeeRewarder when set.
Oracle Data Changes (No Events)
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).
TypeScript Example
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);
}Python Example
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")Key Takeaways
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.
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).
Balance changes have events
Watch Swap, Deposited and Withdrawn for balance-driven price changes.
Price precision is 1e24
Use humanPrice = priceE24 / 1e24 * 10^(decimalsX - decimalsY)
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].
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 /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.
