7. Constant Product Invariant
The AMM constant product curve is the multiplicative analogue of the linear conservation law from §6:
A single monomial, coefficient , exponents — degree 2 in two reserve variables:
const xy = new Polynumber(1n, [1, 1]);
The invariant is locked at pool creation:
const reserveIn = 1000n;
const reserveOut = 500n;
const k = xy.evaluate([reserveIn, reserveOut]); // 500000n
swap: the contract asserts, the caller proves
The key insight: the pool never needs to compute amountOut. The caller proposes both sides of the trade; the pool just checks the invariant holds. No division on-chain.
function swap(
reserveIn: bigint,
reserveOut: bigint,
amountIn: bigint,
amountOut: bigint // proposed by caller
): void {
const k = xy.evaluate([reserveIn, reserveOut]);
const newReserveIn = reserveIn + amountIn;
const newReserveOut = reserveOut - amountOut;
if (xy.evaluate([newReserveIn, newReserveOut]) < k)
throw new Error('invariant violated');
}
No division. The invariant check is two multiplications — the same cross-multiplication pattern as the proportion check in §6.
Note the comparison is < k, not !== k. The caller can understate amountOut and the pool keeps the surplus — that surplus is the fee. Overstating amountOut fires the guard. The pool can only ever grow.
// caller computes off-chain: amountOut = k / newReserveIn = 500000 / 1100 = 454
swap(1000n, 500n, 100n, 454n);
// xy.evaluate([1100n, 46n]) → 1100 * 46 = 50600 ✗ — wrong, caller made an error
// xy.evaluate([1100n, 454n]) → 1100 * 454 = 499400 ≥ 500000? → 499400 < 500000 → guard fires
// xy.evaluate([1100n, 455n]) → 1100 * 455 = 500500 ≥ 500000 ✓ — caller rounds conservatively
swap(1000n, 500n, 100n, 455n); // passes — pool keeps the 500 surplus
The caller bears the rounding responsibility. The contract only ever multiplies.
Confirming the math
For a trade where the division is exact — newReserveIn | k:
// off-chain: 500000 / 1250 = 400 exactly
swap(1000n, 500n, 250n, 100n);
// newReserveIn = 1250n
// newReserveOut = 500n - 100n = 400n
// xy.evaluate([1250n, 400n]) = 500000n === k ✓