JAMMLibrary

Overview

JAMMLibrary is the core calculation library of JAMM DEX, providing various mathematical calculations and utility functions required by the AMM system. It contains key functions such as token address sorting, trading pair address calculation, reserve queries, and price calculations, serving as the mathematical foundation of the entire protocol.

Library Function Categories

JAMMLibrary mainly contains the following categories of functions:

  • Address handling functions

  • Reserve query functions

  • Price calculation functions

  • Multi-hop path calculation functions

Address Handling Functions

Token Address Sorting

function sortTokens(
    address tokenA,
    address tokenB
) internal pure returns (address token0, address token1) {
    require(tokenA != tokenB, "JAMMLibrary: IDENTICAL_ADDRESSES");
    (token0, token1) = tokenA < tokenB
        ? (tokenA, tokenB)
        : (tokenB, tokenA);
    require(token0 != address(0), "JAMMLibrary: ZERO_ADDRESS");
}

Function Description:

  • Ensure two token addresses are not identical

  • Sort by address size, smaller address becomes token0

  • Verify token0 is not zero address

  • Return sorted address pair

Purpose:

  • Ensure trading pair uniqueness

  • Standardize token order in trading pairs

  • Provide consistent input for CREATE2 address calculation

Trading Pair Address Calculation

function pairFor(
    address factory,
    address tokenA,
    address tokenB,
    uint24 fee
) internal pure returns (address pair) {
    (address token0, address token1) = sortTokens(tokenA, tokenB);
    pair = address(
        uint160(
            uint256(
                keccak256(
                    abi.encodePacked(
                        hex"ff",
                        factory,
                        keccak256(abi.encodePacked(token0, token1, fee)),
                        INIT_CODE_PAIR_HASH // Actual hash value determined at deployment
                    )
                )
            )
        )
    );
}

CREATE2 Address Calculation:

  • 0xff: CREATE2 opcode prefix

  • factory: Factory contract address

  • salt: Hash value composed of token0, token1, and fee

  • INIT_CODE_PAIR_HASH: Initialization code hash of JAMMPair contract (actual value determined at deployment)

Features:

  • Deterministic address generation

  • Calculate address without calling Factory contract

  • Support offline address pre-calculation

Reserve Query Functions

Get Reserves

function getReserves(
    address factory,
    address tokenA,
    address tokenB,
    uint24 fee
) internal view returns (uint reserveA, uint reserveB) {
    (address token0, ) = sortTokens(tokenA, tokenB);
    address pair = pairFor(factory, tokenA, tokenB, fee);
    (uint reserve0, uint reserve1, ) = IJAMMPair(pair).getReserves();
    (reserveA, reserveB) = tokenA == token0
        ? (reserve0, reserve1)
        : (reserve1, reserve0);
}

Function Flow:

  1. Sort token addresses

  2. Calculate trading pair address

  3. Call trading pair contract to get reserves

  4. Return corresponding reserves based on token order

Return Values:

  • reserveA: Reserve amount of tokenA

  • reserveB: Reserve amount of tokenB

Price Calculation Functions

Proportional Calculation

function quote(
    uint amountA,
    uint reserveA,
    uint reserveB
) internal pure returns (uint amountB) {
    require(amountA > 0, "JAMMLibrary: INSUFFICIENT_AMOUNT");
    require(
        reserveA > 0 && reserveB > 0,
        "JAMMLibrary: INSUFFICIENT_LIQUIDITY"
    );
    amountB = (amountA * reserveB) / reserveA;
}

Calculation Formula:

amountB = amountA × reserveB / reserveA

Purpose:

  • Calculate optimal ratio when adding liquidity

  • Price queries and display

  • Proportional calculation when removing liquidity

Output Amount Calculation

function getAmountOut(
    uint amountIn,
    uint reserveIn,
    uint reserveOut,
    uint24 fee
) internal pure returns (uint amountOut) {
    require(amountIn > 0, "JAMMLibrary: INSUFFICIENT_INPUT_AMOUNT");
    require(
        reserveIn > 0 && reserveOut > 0,
        "JAMMLibrary: INSUFFICIENT_LIQUIDITY"
    );
    uint amountInWithFee = amountIn * (10000 - fee);
    uint numerator = amountInWithFee * reserveOut;
    uint denominator = reserveIn * 10000 + amountInWithFee;
    amountOut = numerator / denominator;
}

Calculation Formula:

amountInWithFee = amountIn × (10000 - fee)
amountOut = (amountInWithFee × reserveOut) / (reserveIn × 10000 + amountInWithFee)

Fee Handling:

  • Deduct trading fee from input amount

  • Fee expressed in basis points (10000 = 100%)

  • Amount after fee deduction participates in AMM calculation

Input Amount Calculation

function getAmountIn(
    uint amountOut,
    uint reserveIn,
    uint reserveOut,
    uint24 fee
) internal pure returns (uint amountIn) {
    require(amountOut > 0, "JAMMLibrary: INSUFFICIENT_OUTPUT_AMOUNT");
    require(
        reserveIn > 0 && reserveOut > 0,
        "JAMMLibrary: INSUFFICIENT_LIQUIDITY"
    );
    uint numerator = reserveIn * amountOut * 10000;
    uint denominator = (reserveOut - amountOut) * (10000 - fee);
    amountIn = (numerator / denominator) + 1;
}

Calculation Formula:

amountIn = (reserveIn × amountOut × 10000) / ((reserveOut - amountOut) × (10000 - fee)) + 1

Precision Handling:

  • Add 1 to result to ensure sufficient input amount

  • Avoid swap failures due to precision loss

Multi-hop Path Calculation

Multi-hop Output Calculation

function getAmountsOut(
    address factory,
    uint amountIn,
    address[] memory path,
    uint24[] memory fees
) internal view returns (uint[] memory amounts) {
    require(path.length >= 2, "JAMMLibrary: INVALID_PATH");
    amounts = new uint[](path.length);
    amounts[0] = amountIn;
    for (uint i; i < path.length - 1; i++) {
        (uint reserveIn, uint reserveOut) = getReserves(
            factory,
            path[i],
            path[i + 1],
            fees[i]
        );
        amounts[i + 1] = getAmountOut(
            amounts[i],
            reserveIn,
            reserveOut,
            fees[i]
        );
    }
}

Calculation Flow:

  1. Verify path length is at least 2

  2. Initialize amounts array, first element is input amount

  3. Iterate through each hop in the path

  4. Get reserves for current hop

  5. Calculate output amount for current hop

  6. Output amount becomes input for next hop

Multi-hop Input Calculation

function getAmountsIn(
    address factory,
    uint amountOut,
    address[] memory path,
    uint24[] memory fees
) internal view returns (uint[] memory amounts) {
    require(path.length >= 2, "JAMMLibrary: INVALID_PATH");
    amounts = new uint[](path.length);
    amounts[amounts.length - 1] = amountOut;
    for (uint i = path.length - 1; i > 0; i--) {
        (uint reserveIn, uint reserveOut) = getReserves(
            factory,
            path[i - 1],
            path[i],
            fees[i - 1]
        );
        amounts[i - 1] = getAmountIn(
            amounts[i],
            reserveIn,
            reserveOut,
            fees[i - 1]
        );
    }
}

Calculation Flow:

  1. Verify path length

  2. Initialize amounts array, last element is desired output amount

  3. Iterate backward through the path

  4. Get reserves for current hop

  5. Calculate required input amount for current hop

  6. Continue forward calculation until first hop

Usage Examples

Basic Price Queries

// Calculate proportional amount
const amountB = await JAMMLibrary.quote(
    ethers.utils.parseEther("1"), // 1 tokenA
    reserveA,
    reserveB
);

// Calculate swap output
const amountOut = await JAMMLibrary.getAmountOut(
    ethers.utils.parseEther("1"), // Input 1 token
    reserveIn,
    reserveOut,
    100 // 1% fee rate
);

// Calculate required input
const amountIn = await JAMMLibrary.getAmountIn(
    ethers.utils.parseEther("1"), // Desired output 1 token
    reserveIn,
    reserveOut,
    100 // 1% fee rate
);

Multi-hop Path Calculation

// Calculate multi-hop output
const path = [tokenA, tokenB, tokenC];
const fees = [100, 200]; // 1% and 2% fee rates
const amountIn = ethers.utils.parseEther("1");

const amounts = await JAMMLibrary.getAmountsOut(
    factoryAddress,
    amountIn,
    path,
    fees
);

console.log("Input amount:", ethers.utils.formatEther(amounts[0]));
console.log("Intermediate amount:", ethers.utils.formatEther(amounts[1]));
console.log("Output amount:", ethers.utils.formatEther(amounts[2]));

// Calculate multi-hop input
const amountOut = ethers.utils.parseEther("1");
const amountsIn = await JAMMLibrary.getAmountsIn(
    factoryAddress,
    amountOut,
    path,
    fees
);

console.log("Required input:", ethers.utils.formatEther(amountsIn[0]));

Address Calculation

// Calculate trading pair address
const pairAddress = await JAMMLibrary.pairFor(
    factoryAddress,
    tokenA,
    tokenB,
    100 // 1% fee rate
);

// Get reserves
const [reserveA, reserveB] = await JAMMLibrary.getReserves(
    factoryAddress,
    tokenA,
    tokenB,
    100
);

Mathematical Principles

AMM Pricing Formula

JAMM DEX uses the constant product formula:

x × y = k

When considering fees:

(x + Δx × (1 - fee)) × (y - Δy) = k

Where:

  • x, y: Reserves before swap

  • Δx: Input amount

  • Δy: Output amount

  • fee: Trading fee rate

Price Impact

Price impact can be calculated using the following formula:

Price Impact = 1 - (Actual Price / Theoretical Price)

Where:

  • Actual Price = Δy / Δx

  • Theoretical Price = y / x

Slippage Calculation

Slippage is the difference between expected and actual execution price:

Slippage = |Actual Output - Expected Output| / Expected Output

Last updated