Перейти к основному содержимому

Переход свопов ликвидности с 1inch на 0x

Метаданные​

ПараметрЗначение
Дата создания2026-09-17
Дата последнего изменения2026-09-17
Статус апрува⏳ На рассмотрении
Дата апрува—

Продукты ликвидности (NFT-позиции MagnetLiquidityShared / MagnetLiquidityPersonal, земля MagnetLiquidityGround) использовали 1inch: classic Swap API — для обмена токена оплаты в токены пула внутри контракта, Fusion+ — для кросс-чейн обмена перед покупкой земли. Интеграция переводится на 0x: Swap API v2 (AllowanceHolder) для обмена внутри сети и Cross-Chain API для обмена между сетями.

Попутно исправлена уязвимость 1inch-версии: контракт выдавал роутеру апрув на весь баланс токена оплаты и возвращал покупателю весь баланс токенов пула, включая накопленные комиссии других держателей и партнёрские начисления.

MagnetIndexManager агрегатор не использует: позиции индекса создаются из уже имеющихся у пользователя токенов пула, поэтому контракт не менялся.


2.1. Пользовательские сценарии​

  1. Покупка NFT ликвидности (общий пул). Фронт запрашивает у бэкенда calldata 0x для каждого плеча (POST /liquidity/buy, from = адрес контракта), подпись покупки (/liquidity/shared/buy/sign) и вызывает MagnetLiquidityShared.buyNft со swapData0/swapData1.
  2. Покупка персональной NFT. То же, но MagnetLiquidityPersonal.buyNftWithZeroEx (ранее buyNftWith1inch, сигнатура не изменилась).
  3. Кросс-чейн обмен перед покупкой земли. Фронт запрашивает GET /zero-ex/cross-chain/quotes, при необходимости выдаёт апрув на allowanceTarget, отправляет quotes[0].transaction из кошелька пользователя и опрашивает GET /zero-ex/cross-chain/status до isTerminal = true.
  4. Обычный своп из кошелька. GET /zero-ex/swap/price для отображения курса, GET /zero-ex/swap/quote для исполнения: апрув на allowanceTarget, затем отправка transaction.

2.2. Бизнес-логика​

  • Контракт исполняет только AllowanceHolder.exec(operator, token, amount, target, data): селектор, продаваемый токен (= payToken пула) и сумма проверяются до выдачи апрува.
  • Бюджет свопов — сумма оплаты после распределений при покупке (netAmount). Сумма amount двух плеч не может его превышать (ZeroExBudgetExceeded).
  • Учёт по дельтам балансов: балансы контракта до покупки (комиссии держателей, партнёрские начисления) не участвуют ни в свопе, ни в ликвидности, ни в возврате остатка.
  • Остатки покупки (пыль токенов пула и неизрасходованный payToken, если он не токен пула) возвращаются покупателю.
  • Пустое swapData пропускает плечо: так задаётся случай, когда payToken совпадает с токеном пула. Если оба swapData пустые, Shared.buyNft работает по прежнему Uniswap-пути.
  • Адрес AllowanceHolder задаёт админ (setZeroExAllowanceHolder), без него покупка через 0x отклоняется (ZeroExAllowanceHolderNotSet).

2.3. UI/UX требования​

Фронтенд подключается отдельной задачей. Изменения API для фронта:

БылоСтало
POST /liquidity/buy → { to, data } (1inch)POST /liquidity/buy → { to, data, buyAmount, minBuyAmount } (0x). Новые параметры: txOrigin, slippageBps; disableEstimate удалён
POST /liquidity/oneinch/proxy (Fusion+ SDK)GET /zero-ex/cross-chain/quotes, GET /zero-ex/cross-chain/status
personal.buyNftWith1inch(...)personal.buyNftWithZeroEx(...)
setOneInchRouter / oneInchRouter()setZeroExAllowanceHolder / zeroExAllowanceHolder()

Сумма свопов в POST /liquidity/buy должна считаться от netAmount = payAmount − распределения при покупке, а не от payAmount.


3.1. Архитектура​

Frontend ──► backend /liquidity/buy ──► ZeroExSwapService ──► api.0x.org/swap/allowance-holder/quote
│ (taker = контракт ликвидности)
└──► MagnetLiquidityShared.buyNft ─delegatecall─► SharedOps ─delegatecall─► ZeroExSwapLib
│ approve(amount)
▼
0x AllowanceHolder.exec ─► Settler

Frontend ──► backend /zero-ex/cross-chain/quotes|status ──► api.0x.org/cross-chain/*

3.2. Описание технической реализации​

Контракты (apps/contracts-v2/contracts/MagnetLiquidity):

  • libs/ZeroExSwapLib.sol — внешняя (linked) библиотека: swapPayTokenToPoolTokens и refundLeftovers. Вынесена отдельно, чтобы не увеличивать MagnetLiquiditySharedOps: он упирается в лимит 24 KB (после изменения 23 439 байт).
  • storage/BaseLiquidityStorage.sol — слот oneInchRouter переименован в zeroExAllowanceHolder (тип и позиция те же, @custom:oz-renamed-from).
  • MagnetLiquiditySharedOps.buyNft, MagnetLiquidityPersonal.buyNftWithZeroEx — используют библиотеку.
  • scripts/upgradeLiquidityZeroEx.ts — апгрейд развёрнутых прокси.

Бэкенд (apps/backend/src):

  • zeroEx/ — модуль 0x: клиент с ретраями, ZeroExSwapService (price/quote), ZeroExCrossChainService (quotes/status), контроллер /zero-ex/*.
  • liquidity/ — POST /liquidity/buy строит calldata через ZeroExSwapService и проверяет, что транзакция адресована AllowanceHolder; прокси Fusion+ удалён.
  • Переменная окружения ZERO_EX_API_KEY (секрет CI добавлен в scripts/ci-initialize-env.sh).

4. Проблемы и компромиссы​

4.1. Известные ограничения​

  1. Котировка 0x живёт секунды

    • Calldata привязана к taker (контракту) и текущему состоянию ликвидности.
    • Фронт должен запрашивать /liquidity/buy непосредственно перед отправкой транзакции.
  2. MagnetLiquidityPersonal больше 24 KB (30 079 байт; до изменений было 31 147)

    • Ограничение существовало и до миграции; в mainnet имплементацию не задеплоить без дробления контракта.
  3. Fork-тесты зависят от живых источников ликвидности

    • PMM-источники (например, LunarBase) на форке ревертят из-за устаревшего оракула; в тестах они исключены, тесты перезапускаются на свежем блоке.
  4. Токены без комиссии за перевод

    • Распределения и бюджет свопов считаются от payAmount; fee-on-transfer токены в пулах не поддерживаются.
  5. Трансфер NFT Shared внутри покупки запрещён

    • Хук трансфера собирает комиссии пула; вызов из маршрута свопа блокируется (TransferNotAllowed).

4.2. Технический долг​

  • Модуль oneInch и SwapFeeVault (страница /finances/swap) остаются на 1inch — вне скоупа этой задачи.
  • Подключение фронтенда к новым эндпоинтам и методам контрактов.

4.3. Риски​

  • Апгрейд прокси без последующего setZeroExAllowanceHolder оставит в слоте адрес 1inch-роутера: calldata 0x на него не пройдёт. Скрипт апгрейда выставляет адрес в той же операции.

5. Вопросы на дополнительное обсуждение​

  • Нужна ли комиссия интегратора 0x (swapFeeBps/feeBps) для кросс-чейн обмена и свопов из кошелька?
    • Кому адресовано: продукт

6. План реализации​

6.1. Этапы разработки​

  • Этап 1: Контракты + библиотека ZeroExSwapLib
  • Этап 2: Backend API (/zero-ex/*, /liquidity/buy)
  • Этап 3: Frontend
  • Этап 4: Тестирование (BSC mainnet fork, живой 0x API, апгрейд с 1inch-версии)
  • Этап 5: Апгрейд контрактов (scripts/upgradeLiquidityZeroEx.ts) и секрет ZERO_EX_API_KEY в CI

6.2. Критические зависимости​

  • API-ключ 0x в переменных CI/CD (ZERO_EX_API_KEY).

8. Документация​

  • API документация (Swagger)
  • Обновление Engineering docs — engineering/Контракты/MagnetLiquidity-0x
  • Инструкции для пользователей

9. Ссылки​