const { useState: ilUseState, useMemo: ilUseMemo } = React;
const { Card: IlCard, useMobile: ilUseMobile, TokenPair: IlTokenPair, usePairs: ilUsePairs } = window;
// Volatile pools: x·y = k. Stable pools (Solidly): x³y + xy³ = k, with x, y in 1e18 units of the same numeraire.
function ilVolatile(r) { return (2 * Math.sqrt(r)) / (1 + r) - 1; }
function stableCurve(x, y) { return x * x * x * y + x * y * y * y; }
function stablePrice(x, y) { return (3 * x * x * y + y * y * y) / (x * x * x + 3 * x * y * y); }
// Start from a balanced pool (x = y = 1, price 1), find the point on the same curve where the marginal price equals r.
function ilStable(r) {
if (r === 1) return 0;
const k = stableCurve(1, 1);
let lo = 1e-6, hi = 1e6;
for (let i = 0; i < 200; i++) {
const x = Math.sqrt(lo * hi);
const y = solveY(x, k);
if (stablePrice(x, y) > r) lo = x; else hi = x;
}
const x = Math.sqrt(lo * hi), y = solveY(x, k);
const lp = x * r + y, hold = r + 1;
return lp / hold - 1;
}
function solveY(x, k) {
let lo = 0, hi = Math.max(2, k / (x * x * x));
for (let i = 0; i < 200; i++) {
const y = (lo + hi) / 2;
if (stableCurve(x, y) < k) lo = y; else hi = y;
}
return (lo + hi) / 2;
}
function ilPct(r, stable) { return (stable ? ilStable(r) : ilVolatile(r)) * 100; }
const ilNum = (n, dp = 2) => n.toLocaleString('en-US', { minimumFractionDigits: dp, maximumFractionDigits: dp });
const ilSigned = (n, dp = 2) => (n > 0 ? '+' : '') + ilNum(n, dp);
function IlLabel({ children, accent }) {
return
{children}
;
}
function IlField({ label, value, onChange, unit, accent, hint }) {
return (
);
}
function IlStat({ label, value, sub, tone }) {
const color = tone === 'bad' ? '#ef4444' : tone === 'good' ? '#4ade80' : tone === 'warn' ? '#ffd47c' : 'var(--ink)';
return (
{label}
{value}
{sub &&
{sub}
}
);
}
function IlCurve({ stable, r, yieldPct, accent }) {
const W = 640, H = 220, padL = 44, padR = 12, padT = 12, padB = 28;
const xs = [];
for (let i = 0; i <= 120; i++) xs.push(Math.pow(2, -4 + (8 * i) / 120));
const ils = xs.map(x => ilPct(x, stable));
const minY = Math.min(-1, ...ils, -yieldPct - 1), maxY = Math.max(1, yieldPct + 1);
const px = x => padL + ((Math.log2(x) + 4) / 8) * (W - padL - padR);
const py = y => padT + ((maxY - y) / (maxY - minY)) * (H - padT - padB);
const path = xs.map((x, i) => `${i ? 'L' : 'M'}${px(x).toFixed(1)},${py(ils[i]).toFixed(1)}`).join(' ');
const ticks = [0.0625, 0.25, 0.5, 1, 2, 4, 16];
const rc = Math.min(16, Math.max(0.0625, r));
return (
);
}
function ILScreen({ accent }) {
const isMobile = ilUseMobile();
const { pairs } = ilUsePairs();
const [pairAddr, setPairAddr] = ilUseState(null);
const [stableManual, setStableManual] = ilUseState(false);
const [deposit, setDeposit] = ilUseState('10000');
const [pA, setPA] = ilUseState('0');
const [pB, setPB] = ilUseState('0');
const [days, setDays] = ilUseState('30');
const [feeApr, setFeeApr] = ilUseState('0');
const [emitApr, setEmitApr] = ilUseState('0');
const list = pairs ?? [];
const pair = list.find(p => p.pair_address === pairAddr) ?? null;
const stable = pair ? !!pair.stable : stableManual;
const symA = pair ? pair.token0_symbol : 'TOKEN A';
const symB = pair ? pair.token1_symbol : 'TOKEN B';
const out = ilUseMemo(() => {
const dep = parseFloat(deposit) || 0;
const a = 1 + (parseFloat(pA) || 0) / 100, b = 1 + (parseFloat(pB) || 0) / 100;
if (a <= 0 || b <= 0) return null;
const r = a / b;
const il = (stable ? ilStable(r) : ilVolatile(r));
const hold = dep * (a + b) / 2;
const lp = hold * (1 + il);
const t = Math.max(0, parseFloat(days) || 0) / 365;
const yieldPct = ((parseFloat(feeApr) || 0) + (parseFloat(emitApr) || 0)) * t;
const earned = lp * yieldPct / 100;
const net = lp + earned - hold;
let breakeven = null;
if (yieldPct > 0) {
let lo = 1, hi = 1e6;
for (let i = 0; i < 100; i++) { const m = Math.sqrt(lo * hi); if (-ilPct(m, stable) < yieldPct) lo = m; else hi = m; }
breakeven = Math.sqrt(lo * hi);
}
return { r, il: il * 100, hold, lp, ilUsd: lp - hold, yieldPct, earned, net, netPct: hold ? (net / hold) * 100 : 0, breakeven };
}, [deposit, pA, pB, days, feeApr, emitApr, stable]);
const presets = [-50, -25, 0, 25, 50, 100, 300];
return (
RISK
IMPERMANENT LOSS
What an LP position is worth against simply holding the two tokens, after a price move. Volatile pools follow x·y = k. Stable pools follow x³y + xy³ = k, which keeps quoting near 1:1, so the same price move rebalances you harder and a broken peg costs more than it would in a volatile pool.
IL BY RELATIVE PRICE ({symA} / {symB}) · MARKER = YOUR INPUT ({ilNum(out.r, 3)}x)
IL depends only on the ratio between the two prices, not on the direction of the market. It is realised when you withdraw; fees and emissions accrue regardless. Emissions are counted at their current price and do not compound.