Price Calculation
The exact arithmetic behind _calculate_price, including integer-division behaviour, so an off-chain implementation matches the contract wei for wei.
The function
_calculate_price(supply_lots, delta_lots, is_buy) -> (base, tax, net)
Returns base + tax for buys and base - tax for sells. Both quote functions are thin wrappers that pass current supply.
Step 1 — Scale
n = delta_lots * 1000
x = (supply_lots - INITIAL_SUPPLY_LOTS) * 1000
x is supply above the floor. Sells assert supply_lots > INITIAL_SUPPLY_LOTS first, so the subtraction cannot underflow.
Step 2 — Integration bounds
buy: x_start = x, x_end = x + n
sell: x_start = x - n, x_end = x
Both directions integrate over the same interval, which is what makes a buy and the immediately following sell of the same size symmetric in base.
Step 3 — Base price
quad = PRICE_SLOPE * (x_end² - x_start²) / TWO_TIMES_CAP
linear = P_START * n
base = quad + linear
This is the closed form of integrating a marginal price that rises linearly with supply. Because it is integrated in one step, a trade of any size costs exactly what the same volume would cost as a sequence of single-lot trades — no size discount, no size penalty.
x_end² and x_start² are computed separately and subtracted before the division by TWO_TIMES_CAP. Refactoring to (x_end - x_start)(x_end + x_start) is algebraically identical but changes the rounding, and the result no longer matches the contract. Off-chain implementations must keep this ordering.
Step 4 — Tax rate
avg_supply = (x_start + x_end) / 2
avg_supply = min(avg_supply, ADDITIONAL_CAP_TOKENS_BASE)
tax_rate_bp = T_START_BP - (TAX_DECREASE_BP * avg_supply / ADDITIONAL_CAP_TOKENS_BASE)
tax_rate_bp = max(tax_rate_bp, T_END_BP)
The rate decays linearly from 1,200 (12%) at the floor to 120 (1.2%) at ADDITIONAL_CAP_TOKENS_BASE = 740,000,000, then stays flat. Using the trade's average supply rather than its start or end is what keeps buys and sells symmetric.
Step 5 — Apply
tax = base * tax_rate_bp / 10000
buy: return (base, tax, base + tax)
sell: return (base, tax, base - tax)
Reference implementation
// All arithmetic in BigInt — floor division matches Vyper's //
function calculatePrice(supplyLots, deltaLots, isBuy, chain) {
const { P_START, PRICE_SLOPE } = chain;
const INITIAL_SUPPLY_LOTS = 60_000n;
const ADDITIONAL_CAP = 740_000_000n;
const TWO_TIMES_CAP = 1_480_000_000n;
const T_START_BP = 1200n, T_END_BP = 120n, TAX_DECREASE_BP = 1080n;
const n = deltaLots * 1000n;
const x = (supplyLots - INITIAL_SUPPLY_LOTS) * 1000n;
if (!isBuy && supplyLots <= INITIAL_SUPPLY_LOTS) {
throw new Error("Cannot sell at floor");
}
if (!isBuy && n > x) {
// On-chain `x - n` is uint256 and underflows here, reverting the tx.
// BigInt would silently go negative, so guard explicitly.
throw new Error("Cannot sell more than the supply above the floor");
}
const xStart = isBuy ? x : x - n;
const xEnd = isBuy ? x + n : x;
// Square first, then divide — do not factor this
const quad = (PRICE_SLOPE * (xEnd * xEnd - xStart * xStart)) / TWO_TIMES_CAP;
const linear = P_START * n;
const base = quad + linear;
let avg = (xStart + xEnd) / 2n;
if (avg > ADDITIONAL_CAP) avg = ADDITIONAL_CAP;
let rate = T_START_BP - (TAX_DECREASE_BP * avg) / ADDITIONAL_CAP;
if (rate < T_END_BP) rate = T_END_BP;
const tax = (base * rate) / 10000n;
return isBuy
? { base, tax, total: base + tax }
: { base, tax, proceeds: base - tax };
}# Python ints are arbitrary precision; // is floor division, matching Vyper.
# Every value here is non-negative, so floor and truncation agree.
def calculate_price(supply_lots, delta_lots, is_buy, chain):
P_START, PRICE_SLOPE = chain["P_START"], chain["PRICE_SLOPE"]
INITIAL_SUPPLY_LOTS = 60_000
ADDITIONAL_CAP = 740_000_000
TWO_TIMES_CAP = 1_480_000_000
T_START_BP, T_END_BP, TAX_DECREASE_BP = 1200, 120, 1080
n = delta_lots * 1000
x = (supply_lots - INITIAL_SUPPLY_LOTS) * 1000
if not is_buy and supply_lots <= INITIAL_SUPPLY_LOTS:
raise ValueError("Cannot sell at floor")
if not is_buy and n > x:
# On-chain `x - n` is uint256 and underflows here, reverting the tx.
# Python ints would silently go negative, so guard explicitly.
raise ValueError("Cannot sell more than the supply above the floor")
x_start = x if is_buy else x - n
x_end = x + n if is_buy else x
# Square first, then divide — do not factor this
quad = (PRICE_SLOPE * (x_end * x_end - x_start * x_start)) // TWO_TIMES_CAP
linear = P_START * n
base = quad + linear
avg = (x_start + x_end) // 2
avg = min(avg, ADDITIONAL_CAP)
rate = T_START_BP - (TAX_DECREASE_BP * avg) // ADDITIONAL_CAP
rate = max(rate, T_END_BP)
tax = (base * rate) // 10000
if is_buy:
return {"base": base, "tax": tax, "total": base + tax}
return {"base": base, "tax": tax, "proceeds": base - tax}Every intermediate in the Vyper function is a uint256. A sell larger than the supply above the floor underflows at x - n and reverts. Neither Python ints nor JavaScript BigInts underflow — they go negative and return a meaningless number, and the two languages do not even agree on what that number is, because BigInt division truncates toward zero while Python's // floors. Both samples above guard the case explicitly; keep that guard if you reimplement this.
Use this to preview and to build UI, but read quote_buy_price() for the value you actually send. buy() requires exact payment, so any divergence — a rounding difference, a stale supply read, a chain constant transcribed wrong — is a guaranteed revert rather than a small overpayment.
Chain constants for the above
| Constant | Base | BSC | Polygon | Robinhood | Gnosis |
|---|---|---|---|---|---|
P_START | 1_200_000_000 | 3_600_000_000 | 28_800_000_000_000 | 1_200_000_000 | 2_880_000_000_000 |
PRICE_SLOPE | 8_410_810_800 | 25_232_432_400 | 201_859_459_200_000 | 8_410_810_800 | 20_185_945_920_000 |
Pool-side math
Post-graduation the hooks derive pool depth from liquidity and price, treating the full-range position as a constant-product pool:
native_reserve ≈ L * 2^96 / sqrtPriceX96
token_reserve ≈ L * sqrtPriceX96 / 2^96
These feed three decisions: the fee tier, the MEV cap, and the buy-and-burn versus reinvest branch. The MEV cap inverts constant-product slippage:
cap_bps = live_fee_tier_bps * CAP_MULT_BPS / 100
max_safe = pool_native * cap_bps / (10000 - cap_bps)
which is the largest swap whose price impact stays at or below cap_bps, from slippage = swap / (pool + swap) ≤ cap.
Token-side depth is computed from L and sqrtPriceX96, not from balanceOf on the pool manager. On a singleton pool manager the balance conflates every pool holding that token, which would send the branch decision the wrong way for any token with more than one pool — which, on V4-family chains, is every graduated token.