Разработка DEX на базе AMM

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка DEX на базе AMM
Сложная
от 2 недель до 3 месяцев
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1258
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1170
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    873
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1092
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    563
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    830

Разработка DEX на базе AMM

AMM (Automated Market Maker) — это протокол, который заменяет традиционный order book на математическую формулу. Вместо того чтобы ждать контрагента с противоположным ордером, трейдер торгует против пула ликвидности. Цена определяется алгоритмом в реальном времени. Это изменило DeFi — и понять, как это работает изнутри, критично перед тем как строить собственный DEX.

Математика ценообразования

Constant Product Formula

Uniswap V2 popularized формулу x * y = k, где x и y — резервы двух токенов в пуле, k — инвариант. При любой операции произведение резервов должно оставаться константой (с поправкой на комиссию).

Текущая цена токена X в единицах Y: price = y / x

При свопе: трейдер вносит Δx токена X, получает Δy токена Y:

(x + Δx) * (y - Δy) = k
Δy = y - k / (x + Δx) = y * Δx / (x + Δx)

С учётом комиссии 0.3% (fee = 0.003):

Δy = y * Δx * (1 - fee) / (x + Δx * (1 - fee))

Price impact — насколько сдвигается цена при сделке — прямо пропорционален размеру сделки относительно резервов пула. Это фундаментальное свойство: большие ордера дают плохую цену в малых пулах.

Concentrated Liquidity (Uniswap V3)

Uniswap V3 добавил concentrated liquidity: LP-провайдеры указывают ценовой диапазон [Pa, Pb], в котором работает их ликвидность. Вне диапазона ликвидность "неактивна" и не получает комиссии.

Формулы становятся сложнее. Реальные резервы X и Y в пуле:

x = L * (1/√P - 1/√Pb)
y = L * (√P - √Pa)

Где L — количество ликвидности (виртуальная константа), P — текущая цена.

Это фундаментально меняет экономику: капитальная эффективность LP возрастает в 100-4000 раз по сравнению с V2 для стейблкоин-пар, но LP принимает на себя более сложный impermanent loss профиль.

Curve StableSwap

Для активов с близкой ценой (stablecoins, stETH/ETH) constant product даёт плохой slippage. Curve использует гибридную формулу, комбинирующую constant product и constant sum:

A * n^n * Σxi + D = A * D * n^n + D^(n+1) / (n^n * Πxi)

Где A — amplification coefficient. При A=0 — обычный constant product. При A→∞ — constant sum (нулевой slippage, но нестабильно). Curve балансирует между ними, обеспечивая минимальный slippage в диапазоне около паритета и сходясь к constant product на краях.

Архитектура смарт-контрактов

Core контракты

Типичная архитектура AMM DEX состоит из нескольких уровней:

Factory контракт — реестр и создатель пулов:

contract AmmFactory {
    mapping(address => mapping(address => address)) public getPair;
    address[] public allPairs;

    event PairCreated(address indexed token0, address indexed token1, address pair, uint);

    function createPair(address tokenA, address tokenB)
        external returns (address pair)
    {
        require(tokenA != tokenB, "IDENTICAL_ADDRESSES");
        (address token0, address token1) = tokenA < tokenB
            ? (tokenA, tokenB) : (tokenB, tokenA);
        require(token0 != address(0), "ZERO_ADDRESS");
        require(getPair[token0][token1] == address(0), "PAIR_EXISTS");

        bytes memory bytecode = type(AmmPair).creationCode;
        bytes32 salt = keccak256(abi.encodePacked(token0, token1));
        assembly {
            pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
        }

        IAmmPair(pair).initialize(token0, token1);
        getPair[token0][token1] = pair;
        getPair[token1][token0] = pair;
        allPairs.push(pair);

        emit PairCreated(token0, token1, pair, allPairs.length);
    }
}

CREATE2 важен: адрес пула детерминирован и предсказуем из адресов токенов, это позволяет Router-у вычислять адрес пула без обращения к Factory.

Pair (Pool) контракт — ядро AMM. Хранит резервы, выпускает LP-токены, исполняет свопы:

function swap(
    uint amount0Out,
    uint amount1Out,
    address to,
    bytes calldata data
) external lock {
    require(amount0Out > 0 || amount1Out > 0, "INSUFFICIENT_OUTPUT_AMOUNT");
    (uint112 _reserve0, uint112 _reserve1,) = getReserves();

    // Оптимистичный перевод — сначала отдаём
    if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out);
    if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out);
    if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);

    // Проверяем что баланс сошёлся
    uint balance0 = IERC20(_token0).balanceOf(address(this));
    uint balance1 = IERC20(_token1).balanceOf(address(this));
    uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
    uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
    require(amount0In > 0 || amount1In > 0, "INSUFFICIENT_INPUT_AMOUNT");

    // Проверка инварианта (с учётом комиссии 0.3%)
    uint balance0Adjusted = balance0 * 1000 - amount0In * 3;
    uint balance1Adjusted = balance1 * 1000 - amount1In * 3;
    require(balance0Adjusted * balance1Adjusted >= uint(_reserve0) * _reserve1 * 1000**2, "K");

    _update(balance0, balance1, _reserve0, _reserve1);
}

Паттерн "оптимистичный перевод + проверка инварианта" также является основой flash loans: data.length > 0 позволяет вызвать callback на получателе до проверки — это и есть flash swap.

Router контракт — удобный интерфейс для конечных пользователей. Обрабатывает multi-hop маршруты (A→B→C), slippage protection, deadline.

LP-токены и управление ликвидностью

При добавлении ликвидности LP получает ERC-20 токены, представляющие долю пула:

LP_minted = totalSupply * min(amount0 / reserve0, amount1 / reserve1)

При первом добавлении: LP_minted = sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY

MINIMUM_LIQUIDITY = 1000 — эти LP-токены навсегда сжигаются при создании пула, предотвращая атаку на пустой пул.

Оракулы цен

TWAP (Time-Weighted Average Price)

Uniswap V2 ввёл on-chain price oracle через накопление cumulative price:

price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;

TWAP за период = разница cumulativeLast / время периода. Это манипуляционно-устойчивый оракул: чтобы исказить TWAP за 1 час, нужно держать неверную цену весь час, что экономически нецелесообразно.

Uniswap V3 усовершенствовал систему: хранит ring buffer из 65535 наблюдений, tick-based accumulator вместо price accumulator — точнее при concentrated liquidity.

Защита от MEV и атак

Sandwich attacks

Классическая атака: MEV-бот видит pending своп, вставляет свою покупку до него (front-run) и продажу после (back-run), выжимая прибыль за счёт жертвы.

Защитные механизмы:

Slippage tolerance — пользователь задаёт максимальный acceptable price impact (обычно 0.5-1%). При превышении транзакция ревертируется.

Private mempool / Flashbots Protect — отправка транзакций в приватный мемпул, недоступный MEV-ботам. На уровне DEX-фронтенда можно использовать Flashbots rpc endpoint.

Commit-reveal схемы — пользователь сначала публикует хеш намерения, потом раскрывает параметры. Усложняет но не исключает MEV.

CoW Protocol / batch auctions — принципиальное решение: все ордера за период исполняются по uniform clearing price, front-running внутри batch невозможен.

Reentrancy protection

Пул использует nonReentrant mutex (паттерн lock в Uniswap):

uint private unlocked = 1;
modifier lock() {
    require(unlocked == 1, "LOCKED");
    unlocked = 0;
    _;
    unlocked = 1;
}

Оптимизация: использование одного storage slot вместо OpenZeppelin ReentrancyGuard — экономия ~2300 gas на каждом вызове.

Токеномика и управление протоколом

Protocol fee

Uniswap V2 ввёл механизм "protocol fee switch": помимо 0.3% LP fee, может включаться 0.05% protocol fee, идущий в treasury/governance. По умолчанию выключен — включается governance голосованием.

Для собственного AMM: рекомендуем изначально закладывать двухуровневую структуру комиссий (LP fee + protocol fee) даже если protocol fee = 0 при запуске. Добавлять его ретроспективно сложнее из-за необходимости апгрейда контрактов.

Governance токен

Паттерн: governance токен (GVN) распределяется через liquidity mining — LP-провайдеры стейкают свои LP-токены в Staking контракт и получают GVN пропорционально доле и времени.

// Упрощённая формула rewards
pendingReward = userLPBalance * (accRewardPerShare - userRewardDebt) / 1e12;

accRewardPerShare обновляется при каждом взаимодействии — это паттерн из MasterChef (SushiSwap), де-факто стандарт для liquidity mining.

Фронтенд и UX

Критические компоненты фронтенда для AMM DEX:

  • Swap interface с real-time price impact и minimum received
  • Liquidity management с визуализацией диапазона (для V3-style)
  • Charts — интеграция с The Graph для исторических данных пула
  • Wallet integration — wagmi + viem, поддержка WalletConnect v2
  • Transaction simulation — tenderly simulate перед отправкой (предотвращает неожиданные ревёрты)

The Graph индексирует события пула (Swap, Mint, Burn) и предоставляет GraphQL API для аналитики. Альтернатива — собственный индексер на Ponder или Envio для полного контроля над данными.

Итоговый стек для production AMM DEX: Solidity + Foundry для контрактов, Hardhat для деплоя и тестов, React + wagmi для фронтенда, The Graph для индексации, Tenderly для мониторинга. Аудит обязателен — уязвимость в AMM пуле означает потерю всей TVL.