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

Инструкция по интеграции Frontend с контрактами Magnet Liquidity

Данный документ содержит техническое руководство по взаимодействию с контрактами общей (MagnetLiquidityShared) и личной (MagnetLiquidityPersonal) ликвидности.

1. Окружение и адреса контрактов

Результаты деплоя в тестовой среде (Hardhat Network):

Uniswap V3 (Local)

  • WETH9: 0x5FbDB2315678afecb367f032d93F642f64180aa3
  • Factory: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
  • NonfungiblePositionManager (NPM): 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
  • SwapRouter: 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9
  • Pool (token0/token1/3000): 0xc8291c7d77041373FC8b95E2337CB0D1f2204F2e

Test Tokens

  • token0 (payToken): 0x5FC8d32690cc91D4c39d9d3abcBD16989F875707
  • token1: 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9

Magnet Protocols

  • PartnerChainBridge: 0x9A676e781A523b5d0C0e43731313A708CB607508
  • MagnetLiquidityShared: 0xc6e7DF5E7b4f2A278906862b61205850344D4e7d
  • MagnetLiquidityPersonal: 0x67d269191c92Caf3cD7723F116c85e6E9bf55933

2. Общая ликвидность (MagnetLiquidityShared)

Протокол общей ликвидности использует модель долей (shares). Все NFT в одном пуле делят между собой одну или несколько позиций Uniswap V3.

Пользовательские функции

Покупка NFT

Пользователь платит payToken и получает долю в общем пуле.

  • Метод: buyNft(uint256 poolId, uint256 payAmount, uint256 minShares, uint256 minAmt0, uint256 minAmt1, uint256 deadline)
  • Предварительно: Требуется approve для payToken на адрес контракта.
  • Параметры:
    • poolId: ID пула (в тестовой среде 0).
    • minShares: Минимальное количество долей (защита от проскальзывания при свопе внутри контракта).
    • minAmt0/minAmt1: Ограничения на добавление ликвидности в Uniswap.

Пополнение (Top up)

Добавление средств в существующую NFT (увеличивает количество уже имеющихся долей).

  • Метод: topUp(uint256 nftId, uint256 payAmount, uint256 minAmt0, uint256 minAmt1, uint256 deadline)
  • Важно: В отличие от покупки, при пополнении (top up) партнерские и другие распределения не взимаются.

Вывод средств (Withdraw)

Частичный или полный вывод ликвидности. Требуется подпись бэкенда.

  • Метод: withdraw(uint256 nftId, uint256 shares, bool convertSingle, uint256 minOut, uint256 minAmt0, uint256 minAmt1, uint256 nonce, uint256 deadline, bytes sig)
  • Параметры:
    • shares: Количество долей к выводу.
    • convertSingle: Если true, контракт попытается конвертировать оба токена пары в payoutToken (обычно payToken).
    • sig: Подпись от signatureVerifier, подтверждающая право на вывод.

Клейм дохода (Claim)

Забор накопленных комиссий без уменьшения тела позиции. Требуется подпись бэкенда.

  • Метод: claim(uint256 nftId, bool convertSingle, uint256 minOut, uint256 nonce, uint256 deadline, bytes sig)

Админские и операторские функции

Создание/Настройка пула

  • Метод: setPoolConfig(uint256 poolId, PoolConfig config)
  • Config: включает адреса токенов, NPM, Router, границы тиков (tickLower, tickUpper) и флаг enabled.

Ребалансировка (Rebalance)

Переносит всю активную ликвидность пула в новый диапазон тиков.

  • Метод: rebalance(uint256 poolId, int24 newTickLower, int24 newTickUpper, uint256 minAmt0, uint256 minAmt1, uint256 deadline)
  • Логика: Старая позиция закрывается, средства свопаются под новый диапазон и открывается новая позиция. Старая позиция становится «неактивной».

Установка активной позиции

Если в пуле несколько позиций (например, после частичного перемещения), можно вручную указать, какая из них является «активной» (в неё будут идти новые депозиты).

  • Метод: setActivePosition(uint256 poolId, uint256 positionId)

Реинвестирование (Shared Reinvest)

Реинвестирование накопленных вознаграждений обратно в пул. Доступно в двух режимах:

Ручное реинвестирование — вызывается только владельцем NFT, подпись не требуется.

  • Метод: reinvest(uint256 nftId, uint256 minAmt0, uint256 minAmt1, uint256 deadline)
  • Логика: Забирает pending rewards данной NFT, свопает в оптимальное соотношение и добавляет ликвидность. Доли (shares) пропорционально увеличиваются.
  • Распределения: Не применяются.

Автоматизированное реинвестирование — вызывается оператором/админом с EIP-712 подписью от верификатора.

  • Метод: reinvestByOperator(uint256 nftId, uint256 minAmt0, uint256 minAmt1, uint256 nonce, uint256 deadline, bytes sig)
  • Nonce: Привязан к владельцу NFT (ownerOf(nftId)), а не к msg.sender (оператору).
  • EIP-712 тип: ReinvestPermit(uint256 nftId,uint256 nonce,uint256 deadline)

Перекладывание ликвидности (Move)

Позволяет переместить часть ликвидности из одной позиции в другую (даже в другой пул). Используется для диверсификации.

  • Метод: moveLiquidity(uint256 fromPoolId, uint256 fromPositionId, uint256 toPoolId, uint128 liquidity, uint256 amount0Min, uint256 amount1Min, uint256 deadline)
  • Пример: Вывод 50% ликвидности из пассивной позиции и добавление её в активную позицию другого пула.

Функции чтения (View Shared)

Получение данных пула

  • Метод: poolConfigs(uint256 poolId)
  • Описание: Возвращает структуру конфигурации пула (адреса токенов, NPM, границы тиков активной позиции и т.д.).

Проверка доходности NFT

  • Метод: pendingRewards(uint256 nftId)
  • Описание: Возвращает (uint256 pending0, uint256 pending1) — количество накопленных токенов дохода, доступных для клейма.

Данные о долях и позициях

  • Метод: nftShares(uint256 nftId) — количество долей данной NFT в пуле.
  • Метод: poolTotalShares(uint256 poolId) — общее количество долей всех пользователей в пуле.
  • Метод: nftPoolId(uint256 nftId) — ID пула, к которому привязана NFT.
  • Метод: activePositionTokenId(uint256 poolId) — ID текущей активной позиции в Uniswap V3 (NPM Token ID).
  • Метод: getInactivePositions(uint256 poolId) — список ID старых (неактивных) позиций Uniswap V3, которые еще содержат ликвидность и приносят доход.

3. Личная ликвидность (MagnetLiquidityPersonal)

В этом протоколе каждая NFT — это отдельная, независимая позиция в Uniswap V3.

Пользовательские функции (Personal)

Покупка NFT (Personal)

  • Метод: buyNft(uint256 poolId, uint256 payAmount, int24 tickLower, int24 tickUpper, uint256 minAmt0, uint256 minAmt1, uint256 deadline)
  • Отличие: Пользователь сам указывает диапазон тиков (tickLower, tickUpper).

Пополнение (Personal Top up)

  • Метод: topUp(uint256 nftId, uint256 payAmount, uint256 minAmt0, uint256 minAmt1, uint256 deadline)

Вывод и Клейм (Personal)

Методы идентичны Shared контракту, но оперируют liquidity (Uniswap units) вместо shares.

  • Personal Withdraw: withdraw(uint256 nftId, uint128 liquidity, bool convertSingle, uint256 minOut, uint256 minAmt0, uint256 minAmt1, uint256 nonce, uint256 deadline, bytes sig)
  • Personal Claim: claim(uint256 nftId, bool convertSingle, uint256 minOut, uint256 nonce, uint256 deadline, bytes sig)

Ребалансировка (Personal Rebalance)

Пользователь (владелец NFT) может сам изменить диапазон своей позиции.

  • Метод: rebalance(uint256 nftId, int24 newTickLower, int24 newTickUpper, uint256 minAmt0, uint256 minAmt1, uint256 deadline)

Реинвестирование (Personal Reinvest)

Реинвестирование собранных комиссий обратно в позицию. Доступно в двух режимах:

Ручное реинвестирование — вызывается только владельцем NFT, подпись не требуется.

  • Метод: reinvest(uint256 nftId, uint256 minAmt0, uint256 minAmt1, uint256 deadline)
  • Логика: Собирает накопленные комиссии с позиции Uniswap V3, свопает в оптимальное соотношение и увеличивает ликвидность.
  • Распределения: Не применяются.

Автоматизированное реинвестирование — вызывается оператором/админом с EIP-712 подписью от верификатора.

  • Метод: reinvestByOperator(uint256 nftId, uint256 minAmt0, uint256 minAmt1, uint256 nonce, uint256 deadline, bytes sig)
  • Nonce: Привязан к владельцу NFT (ownerOf(nftId)), а не к msg.sender (оператору).
  • EIP-712 тип: ReinvestPermit(uint256 nftId,uint256 nonce,uint256 deadline)

Функции чтения (View Personal)

Проверка дохода и позиции

  • Метод: nftPositionTokenId(uint256 nftId)
  • Описание: Возвращает NPM Token ID для конкретной NFT. По этому ID можно получить детальные данные позиции напрямую из контракта Uniswap NPM.

Состояние позиции

  • Метод: nftLiquidity(uint256 nftId)
  • Описание: Количество "чистой" ликвидности (units) в позиции Uniswap V3.

Доходность

  • Описание: В личном протоколе доход не аккумулируется в контракте Magnet, а остается в Uniswap до вызова claim. Для получения суммы накопленных комиссий следует использовать метод positions контракта NonfungiblePositionManager (NPM).

4. Общие технические детали

EIP-712 Подписи

Для функций withdraw, claim и claimPartnerRewards требуется подпись от бэкенда (адрес верификатора: 0x90F79bf6EB2c4f870365E785982E1f101E93b906). Domain Separator генерируется при инициализации контракта. Types:

  • WithdrawPermit(uint256 nftId,bool convertToSingleToken,uint256 minOut,uint256 nonce,uint256 deadline)
  • ClaimPermit(uint256 nftId,bool convertToSingleToken,uint256 minOut,uint256 nonce,uint256 deadline)
  • ReinvestPermit(uint256 nftId,uint256 nonce,uint256 deadline) — для автоматизированного реинвестирования через reinvestByOperator. Nonce привязан к владельцу NFT.
  • ClaimPartnerPermit(uint8 maxLines,uint256 nonce,uint256 deadline) — для claimPartnerRewards. Nonce привязан к msg.sender.

Нонсы (Nonces)

У каждого пользователя свой счетчик нонсов для защиты от повторных атак.

  • Проверить текущий нонс: userNonces(address user)

Партнерская программа

Награды партнерам начисляются по линиям в маппинг внутри контракта при покупках и клеймах. Линия определяется глубиной даунлайна относительно получателя: lineIndex 0 — награда от прямого даунлайна (линия 1), lineIndex 1 — от даунлайна второго уровня (линия 2) и т.д.

Идентификатор партнёра — userId из PartnerChainBridge. Хранение ведётся по userId (не по адресу), что позволяет партнёру сменить адрес кошелька без потери накопленных наград.

Чтение баланса

  • Метод: partnerRewardsBalance(uint32 userId, address token, uint8 lineIndex)

  • Описание: Баланс по конкретной линии. lineIndex 0-based (lineIndex 0 = линия 1).

  • Метод: pendingPartnerRewards(uint32 userId, address[] tokens, uint8 maxLines)

  • Описание: Суммарный баланс по всем токенам за линии 0..maxLines-1. Если maxLines = 0 — суммируются все 15 линий.

Клейм партнёрских наград

  • Метод: claimPartnerRewards(address[] tokens, uint8 maxLines, uint256 nonce, uint256 deadline, bytes sig)
  • Требует: EIP-712 подпись от signatureVerifier с типом ClaimPartnerPermit.
  • Параметры:
    • tokens: список токенов для клейма.
    • maxLines: максимальная глубина линий для клейма (1–15). Значение 0 означает клейм по всем 15 линиям.
    • nonce, deadline, sig: данные EIP-712 подписи.
  • Логика: контракт сам определяет userId вызывающего через PartnerChainBridge.userIds(msg.sender). userId не передаётся в параметрах и не входит в EIP-712 структуру.
  • Для работы: вызывающий должен быть зарегистрирован в PartnerChainBridge.

Пример построения подписи (TypeScript)

const structHash = ethers.keccak256(
ethers.AbiCoder.defaultAbiCoder().encode(
['bytes32', 'uint8', 'uint256', 'uint256'],
[CLAIM_PARTNER_PERMIT_TYPEHASH, maxLines, nonce, deadline],
),
);
const digest = ethers.keccak256(
ethers.concat(['0x1901', domainSeparator, structHash]),
);
const sig = await signer.signMessage(ethers.getBytes(digest));
// или: ethers.Signature.from(await signer.signingKey.sign(digest)).serialized

где CLAIM_PARTNER_PERMIT_TYPEHASH = keccak256('ClaimPartnerPermit(uint8 maxLines,uint256 nonce,uint256 deadline)').

Тестовые токены

Для тестов используйте token0 (как основной токен оплаты) и token1. Скрипт развертывания автоматически минтит их деплоеру и создает пул с ликвидностью для обеспечения работы свопов.

Получение списка NFT пользователя

Контракты поддерживают стандарт ERC721Enumerable, что позволяет легко получить все NFT пользователя:

  1. balanceOf(address owner) — сколько всего NFT у пользователя.
  2. tokenOfOwnerByIndex(address owner, uint256 index) — получить ID NFT по индексу (от 0 до balance-1).

Получение деталей позиции из Uniswap

Для получения текущей стоимости позиции во фронтенде (сколько токенов 0 и 1 сейчас в позиции) используйте адрес NPM из конфига пула:

  • Контракт: NonfungiblePositionManager
  • Метод: positions(uint256 tokenId)
  • Возвращаемые данные: tickLower, tickUpper, liquidity, tokensOwed0, tokensOwed1 и др.