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

MagnetNftProduct

Обзор

MagnetNftProduct — реюзабельный контракт продуктовых NFT. Он объединяет ERC721 (enumerable), контроль ролей и отдельную финансовую логику для учета/распределения средств на уровне конкретного NFT.

Для каждого продукта деплоится отдельный контракт NFT.

Архитектура

Модуль состоит из трех контрактов:

  1. MagnetNftProduct.sol — основной ERC721, роли, минт/апгрейд, ограничения трансфера, вызовы финансовой логики.
  2. MagnetNftProductFInanceLogic.sol — финансовая логика (пополнение/вывод/распределение).
  3. MagneNftProductStorage.sol — общее хранилище, события и ошибки (важно для корректного стораджа при delegatecall).

Роли и права

  • DEFAULT_ADMIN_ROLE — настройка параметров контракта (white-list, домен EIP‑712, финансовая логика, разрешенные токены/контракты, конфиг распределения).
  • MINTER_ROLE — минт и апгрейд уровня (покупка/крафт/бизнес‑логика продукта).
  • BURNER_ROLE — сжигание без требования владения (используется в крафте/сервисной логике).
  • DIAMOND_CONTRACT_ROLE — diamond контракт для EIP‑712 операций (например, reload) и восстановлений после смены кошелька.

Концепции

  • Root NFT: первый NFT продукта на кошельке.
    • хранится как rootToken[user];
    • для root токена трансфер обычно запрещен (isTransferAvailable[tokenId] = false);
    • при апгрейде уровня/прокачке чаще всего опираемся именно на root.
  • Минт: два режима выпуска NFT.
    • минт по подписи (клейм) — пользователь вызывает mintProductBySignature с подписью бекенда;
    • минт по роли (on-chain) — mintProduct вызывается контрактами/сервисами с MINTER_ROLE (покупка, крафт).

Структуры

DistributionConfig

Сигнатура:

struct DistributionConfig { bool distributionType; uint8 feePercent; uint16 percentToBurn; uint16[] partnerPercents; }

Назначение: правила распределения средств при _distribute.

  • distributionType = true — партнерская схема (переводы партнерам из diamond по partnerPercents).
  • distributionType = false — прямой режим: выплата владельцу NFT минус fee и burn.

Примечание: проценты заданы в тысячных долях HUNDRED_PERCENT = 1000.

События

ProductMinted

event ProductMinted(address indexed user, uint256 indexed tokenId, bytes objectId, bool transferProhibited, bool isRootToken, uint256 level);

Эмитится при mintProductBySignature и mintProduct.

LevelUp

event LevelUp(uint256 indexed tokenId, uint256 prevLevel, uint256 newLevel);

Эмитится при upgradeTokenLevel.

Reload

event Reload(address indexed product, uint256 indexed tokenId, address token, uint256 amount, uint256 timestamp);

Эмитится при reload.

TransferWhitelisted

event TransferWhitelisted(address indexed addr, bool isWhitelisted);

Эмитится при setTransferWhitelist.

RefilFromWallet

event RefilFromWallet(uint256 indexed tokenId, uint256 indexed walletTokenId, address indexed token, uint256 amount);

Эмитится при refillFromWallet.

RefilFromWalletNative

event RefilFromWalletNative(uint256 indexed tokenId, uint256 indexed walletTokenId, uint256 amount);

Эмитится при refillFromWalletNative.

Refil

event Refil(uint256 indexed tokenId, address indexed tokenAddress, uint256 amount);

Эмитится при refill.

NativeRefilled

event NativeRefilled(uint256 indexed tokenId, uint256 amount);

Эмитится при refillNative.

Witdraw

event Witdraw(uint256 indexed tokenId, address indexed tokenAddress, uint256 amount);

Эмитится при withdraw.

NativeWithdrawn

event NativeWithdrawn(address indexed user, uint256 amount);

Эмитится при withdrawNative.

WitdrawByContract

event WitdrawByContract(uint256 indexed tokenId, address indexed tokenAddress, uint256 amount);

Эмитится при withdrawByContract.

Функции: инициализация и интерфейсы

initialize

function initialize() public initializer

Назначение: инициализация прокси, установка ролей и базовых параметров (_tokenIdCounter, signatureVerifier).

supportsInterface

function supportsInterface(bytes4 _interfaceId) public view override returns (bool)

Назначение: поддержка интерфейсов ERC721Enumerable/AccessControl.

_authorizeUpgrade

function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE)

Назначение: ограничить апгрейд реализации администраторами.

Функции: администрирование

setTransferWhitelist

function setTransferWhitelist(address _address, bool _isWhitelisted) external onlyRole(DEFAULT_ADMIN_ROLE)

Назначение: разрешить сервисному адресу инициировать трансферы даже для нетрансферабельных токенов.

setDomainSeparator

function setDomainSeparator(bytes32 _newDomain) external onlyRole(DEFAULT_ADMIN_ROLE)

Назначение: обновить DOMAIN_SEPARATOR для EIP‑712.

setFinancialLogic

function setFinancialLogic(address _newFinancialLogic) external onlyRole(DEFAULT_ADMIN_ROLE)

Назначение: установить контракт финансовой логики (используется через delegatecall).

setSignatureVerifier

function setSignatureVerifier(address _signatureVerifier) external onlyRole(DEFAULT_ADMIN_ROLE)

Назначение: установить адрес подписанта для mintProductBySignature.

setAllowedToken

function setAllowedToken(address _token, bool _access) external onlyRole(DEFAULT_ADMIN_ROLE)

Назначение: разрешить/запретить ERC20 для финансовых операций.

setAllowedContract

function setAllowedContract(address _contract, bool _isAllowed) external onlyRole(DEFAULT_ADMIN_ROLE)

Назначение: разрешить контрактам вызывать withdrawByContract.

setDiamond

function setDiamond(address _newDiamond) external onlyRole(DEFAULT_ADMIN_ROLE)

Назначение: установить diamond и выдать ему роль DIAMOND_CONTRACT_ROLE + setApprovalForAll(diamond, true).

setMagnetNftWalletAddress

function setMagnetNftWalletAddress(address _newAddress) external onlyRole(DEFAULT_ADMIN_ROLE)

Назначение: установить адрес magnetNftWallet для пополнений из внешнего кошелька/контракта.

setEnableWithdraw

function setEnableWithdraw(bool _newInstance) external onlyRole(DEFAULT_ADMIN_ROLE)

Назначение: включить/выключить вывод средств владельцем (withdraw, withdrawNative).

setDistributionConfig

function setDistributionConfig(DistributionConfig calldata _distributionConfig) external onlyRole(DEFAULT_ADMIN_ROLE)

Назначение: задать правила распределения для _distribute.

setFeeReceiver

function setFeeReceiver(address _feeReceiver) external onlyRole(DEFAULT_ADMIN_ROLE)

Назначение: установить distributionFeeReceiver.

Функции: просмотр

tokensOfOwner

function tokensOfOwner(address _owner) external view returns (uint256[] memory)

Назначение: вернуть все tokenId, принадлежащие пользователю.

Функции: минт и апгрейд

mintProductBySignature

function mintProductBySignature(bytes calldata _objectId, bool _transferProhibited, uint _expiration, bytes calldata _signature, uint256 _level) external returns (uint256 tokenId)

Назначение: клейм NFT пользователем по подписи бекенда.

Ключевые проверки:

  • подпись не использована ранее (isSignatureUsed);
  • подпись не просрочена (_expiration);
  • адрес подписанта совпадает с signatureVerifier.

Root-логика:

  • если это первый токен пользователя — становится root и блокируется трансфер.

mintProduct

function mintProduct(address _user, bytes calldata _objectId, bool _transferProhibited, uint256 _level) external onlyRole(MINTER_ROLE) returns (uint256, bool)

Назначение: минт от доверенных контрактов/сервисов.

upgradeTokenLevel

function upgradeTokenLevel(uint256 _tokenId, uint256 _newLevel) external onlyRole(MINTER_ROLE)

Назначение: повысить уровень NFT (уровень может только увеличиваться).

Функции: сжигание и восстановление

burn

function burn(uint _tokenId, address[] calldata erc20token) public

Назначение: сжечь NFT при отсутствии средств на балансе токена.

Правила:

  • если нет BURNER_ROLE, сжигать может только владелец;
  • запрещено сжигать при наличии нативных/erc20 остатков.

multisigRestoreNfts

function multisigRestoreNfts(address oldAddress, address newAddress) external onlyRole(DIAMOND_CONTRACT_ROLE)

Назначение: перенос всех NFT с oldAddress на newAddress (восстановление).

Функции: reload и распределение

reload

function reload(address _product, uint256 _tokenId, address _token, uint256 _amount, uint256 _nonce, uint256 _expiration, bytes calldata _signature) external

Назначение: инициировать «перезагрузку» и распределение _amount по правилам distributionConfig.

Технически:

  • проверяет EIP‑712 подпись (через DOMAIN_SEPARATOR и RECHARGE_TYPEHASH);
  • делегирует распределение в финансовую логику через delegatecall на _distribute.

_distribute

function _distribute(uint256 _tokenId, address _token, uint256 _amount) external

Находится в MagnetNftProductFInanceLogic.sol и вызывается через delegatecall.

Назначение: распределить _amount в токене _token:

  • партнерский режим — выплаты партнерам из diamond;
  • прямой режим — выплата владельцу NFT;
  • fee на distributionFeeReceiver + burn (address(0)).

Функции: финансовая логика

Ниже функции из MagnetNftProductFInanceLogic.sol (как правило вызываются через ABI основного контракта, так как storage общий и используется delegatecall).

refillFromWallet

function refillFromWallet(uint256 _tokenId, uint256 _walletTokenId, address _token, uint256 _amount) public

Назначение: пополнить баланс NFT за счет magnetNftWallet.

refillFromWalletNative

function refillFromWalletNative(uint256 _tokenId, uint256 _walletTokenId, uint256 _amount) public

Назначение: пополнить баланс NFT нативной валютой из magnetNftWallet.

refill

function refill(uint _tokenId, address _tokenAddress, uint _amount) external

Назначение: пополнить баланс NFT ERC20 токеном пользователем (approve обязателен).

refillNative

function refillNative(uint256 _tokenId) external payable

Назначение: пополнить баланс NFT нативной валютой.

withdraw

function withdraw(uint _tokenId, address _tokenAddress, uint _amount) external

Назначение: вывести ERC20 владельцем (требует enableWithdraw = true).

withdrawNative

function withdrawNative(uint _tokenId, uint256 _amount) external

Назначение: вывести нативную валюту владельцем (требует enableWithdraw = true).

withdrawByContract

function withdrawByContract(uint _tokenId, address _tokenAddress, uint _amount) external

Назначение: вывести ERC20 доверенным контрактом из allowedContracts.

Функции: хуки и проксирование

_transfer

function _transfer(address from, address to, uint256 tokenId) internal virtual override

Назначение: блокировать трансфер для root/нетрансферабельных токенов, кроме whitelist.

fallback

fallback() external payable

Назначение: проксировать неизвестные вызовы на financialLogic через delegatecall.

receive

receive() external payable {}

Назначение: принять нативную валюту.

Связь с бекендом и оплатами

Сценарии клейма по подписи используют mintProductBySignature и подпись, которую генерирует бекенд, чтобы создать NFT с заранее заданными параметрами.