MagnetNftProduct
Обзор
MagnetNftProduct — реюзабельный контракт продуктовых NFT. Он объединяет ERC721 (enumerable), контроль ролей и отдельную финансовую логику для учета/распределения средств на уровне конкретного NFT.
Для каждого продукта деплоится отдельный контракт NFT.
Архитектура
Модуль состоит из трех контрактов:
MagnetNftProduct.sol— основной ERC721, роли, минт/апгрейд, ограничения трансфера, вызовы финансовой логики.MagnetNftProductFInanceLogic.sol— финансовая логика (пополнение/вывод/р аспределение).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 с заранее заданными параметрами.