MagnetIndexManager
Обзор
MagnetIndexManager — контракт-менеджер пользовательских Uniswap V3 позиций, реализованный как ERC721 NFT контракт. Он выступает прослойкой между пользователем и NonfungiblePositionManager, токенизирует владение позициями через собственные NFT (MIDX) и ограничивает работу только с заранее одобренными пулами.
Ключевые свойства:
- upgradeable (UUPS);
- ERC721 NFT (каждая позиция представлена уникальным токеном);
- защита от реентранси (
ReentrancyGuardUpgradeable); - админские операции через
AccessControlUpgradeable; - whitelist пулов Uniswap V3;
- per-user opt-in на управление позициями оператором + поддержка стандартных ERC721 approvals;
- распределение rewards по партнерским кошелькам через backend-signed список при
claimFeesиclosePosition.
Контекст и зависимости
- Uniswap V3 NonfungiblePositionManager — основной внешний контракт, через который создаются, увеличиваются, уменьшаются, клеймятся и закрываются позиции.
- ERC20 токены пула — пользователь должен предварительно сделать
approveдляMagnetIndexManager. - Бэкенд / off-chain validator — подписывает разрешенный список партнерских кошельков для
claimFeesиclosePosition.
Контракт не выполняет свапы и не подбирает диапазон тиков автоматически. Он управляет уже заданными пользователем позициями.
Роли и права
-
DEFAULT_ADMIN_ROLE:- управление whitelist пулов;
- настройка процентов партнерского распределения;
- апгрейд реализации (UUPS);
- выдача ролей
POSITION_OPERATORиSIGNATURE_VALIDATOR.
-
POSITION_OPERATOR:- может управлять позицией пользователя только если пользователь явно включил это через
setUserOperatorEnabled(true).
- может управлять позицией пользователя только если пользователь явно включил это через
-
SIGNATURE_VALIDATOR:- роль для адресов, чьей EIP-712 подписью авторизуется список партнеров в
claimFeesиclosePosition.
- роль для адресов, чьей EIP-712 подписью авторизуется список партнеров в
Состояние
DOMAIN_SEPARATOR— EIP-712 domain separator для проверки подписейclaimFees.whitelistedPools[poolKey]— разрешен ли пул для создания новых позиций.positionPools[tokenId]— конфигурация пула (npm,token0,token1,fee) для позиции.positionLiquidity[tokenId]— последняя известная ликвидность позиции.userOperatorEnabled[user]— дал ли пользователь право операторам Magnet управлять его позициями.userNonces[user]— nonce для replay-protection вclaimFeesиclosePosition.partnerPercents— массив процентов в basis points для партнерского распределения.
Ограничения и безопасность
- Создание только в whitelist пулах. Новую позицию можно создать только если admin заранее разрешил соответствующую комбинацию
npm + token0 + token1 + fee. - NFT-владение. При создании позиции пользователь получает NFT (
MIDX) с ID, равным ID позиции в Uniswap V3. Владение этой NFT дает право на управление позицией и получение средств. - Порядок токенов. Для конфигурации пула требуется
token0 < token1, как и в Uniswap V3. - Operator access opt-in & ERC721 Approvals. Управлять позицией может:
- владелец NFT;
- оператор с ролью
POSITION_OPERATOR, если владелец включилuserOperatorEnabled; - адрес, имеющий стандартный
approveилиsetApprovalForAllна данный NFT.
- Claim/close only with signed partners list. Распределение партнерки выполняется только если список партнерских кошельков подписан адресом с ролью
SIGNATURE_VALIDATOR. - Replay-защита.
claimFeesиclosePositionиспользуютuserNonces[user]; одна и та же подпись не может быть использована повторно. - Соответствие массивов. Для
claimFeesиclosePositionдлинаpartnersдолжна строго совпадать с длинойpartnerPercents. - Нет встроенног о referral registry. Контракт не знает, кто является партнером пользователя; он доверяет только подписанному списку кошельков.
- Частичный withdraw не должен выводить claim fees в обход партнерки. При
decreaseLiquidityконтракт собирает только amounts, возвращенные изdecreaseLiquidity, а накопленные fees остаются для последующегоclaimFees. - Закрытие позиции требует подписанный список партнеров.
closePositionможет materialize накопленные fees, поэтому он также проверяет backend-подпись партнерского списка и распределяет fee-часть по партнерке.
Ошибки
IndexAccessDenied
Реверт если вызывающий не является владельцем позиции и не имеет допустимого операторского доступа.
IndexDeadlineExpired
Реверт если операция вызвана после deadline.
IndexInvalidLiquidity
Реверт если liquidity равна нулю, превышает доступную или если mint/increase не создали ликвидность.
IndexInvalidPartners
Реверт если:
- сумма
partnerPercentsбольше 100%; - длина массива партнеров не совпадает с длиной массива процентов;
- один из партнерских адресов равен
address(0).
IndexInvalidPool
Реверт если pool config содержит нулевые адреса.
IndexInvalidSignature
Реверт если подпись claimFees/closePosition невалидна, nonce не совпадает или подпись уже была использована.
IndexInvalidTokenOrder
Реверт если token0 >= token1.
IndexPositionNotFound
Реверт если позиция с таким tokenId не существует (не сминтили NFT).
IndexPoolNotWhitelisted
Реверт если пользователь пытается создать позицию в неразрешенном пуле.
Структуры
IndexPoolParams
Описание пула Uniswap V3:
npm— адресNonfungiblePositionManager;token0,token1— токены пула;fee— fee tier пула.
IndexCreatePositionParams
Параметры для создания позиции:
- конфиг пула;
tickLower,tickUpper;- желаемые amounts и min amounts;
deadline.
IndexIncreaseLiquidityParams
Параметры добавления ликвидности:
tokenId;amount0Desired,amount1Desired;amount0Min,amount1Min;deadline.
IndexDecreaseLiquidityParams
Параметры вывода ликвидности:
tokenId;liquidity;amount0Min,amount1Min;deadline.
IndexClaimFeesParams
Параметры клейма комиссий:
tokenId;partners— список партнерских кошельков;nonce;deadline;signature— EIP-712 подпись отSIGNATURE_VALIDATOR.
IndexClosePositionParams
Параметры полного закрытия позиции:
tokenId;amount0Min,amount1Min;partners— список партнерских кошельков для fee-части;nonce;deadline;signature— EIP-712 подпись отSIGNATURE_VALIDATOR.
UserPositionInfo
Информация о позиции пользователя:
tokenId— ID позиции в Uniswap V3;owner— владелец позиции;pool— конфигурация пула (IndexPoolParams);liquidity— текущая ликвидность позиции.
События
PoolWhitelistUpdated
event PoolWhitelistUpdated(bytes32 indexed poolKey, bool whitelisted);
Эмитится при изменении whitelist статуса пула.
PartnerPercentsUpdated
event PartnerPercentsUpdated(uint16[] percents);
Эмитится при обновлении процентов партнерского распределения.
UserOperatorEnabled
event UserOperatorEnabled(address indexed user, bool enabled);
Эмитится когда пользователь включает или выключает управление от кошельков Magnet.
ProductMinted
event ProductMinted(address indexed user, uint256 indexed tokenId, bytes objectId, bool transferProhibited, bool isRootToken, uint256 level);
Эмитится при минте NFT позиции. Используется для индексации позиций. Поля objectId, transferProhibited, isRootToken и level в данном контракте являются вспомогательными и передаются как значения по умолчанию ("", false, false, 0).
PositionCreated
event PositionCreated(uint256 indexed tokenId, address indexed owner, bytes32 indexed poolKey, uint128 liquidity, uint256 amount0, uint256 amount1);
Эмитится при успешном mint новой позиции.
PositionLiquidityIncreased
event PositionLiquidityIncreased(uint256 indexed tokenId, address indexed owner, uint128 liquidity, uint256 amount0, uint256 amount1);
Эмитится при успешном добавлении ликвидности.