---
title: "Понимание стейкинга NFT: механизмы и преимущества"
date: 2024-05-30
description: "Как организовать стейкинг NFT на смарт-контрактах, как рассчитываются вознаграждения и какую пользу это может принести проектам и держателям NFT. "
author: "Павел Найданов"
intro_image: "https://metalamp.ru/images/stejking_nft.png"
fulltext_image: "https://metalamp.ru/images/stejking_nft.png"
categories:
  - name: "Magazine"
    url: "https://metalamp.ru/magazine.md"
tags:
  - name: "ethereum"
    url: "https://metalamp.ru/tags/ethereum.md"
  - name: "web3"
    url: "https://metalamp.ru/tags/web3.md"
  - name: "dApps"
    url: "https://metalamp.ru/tags/dapps.md"
  - name: "nft"
    url: "https://metalamp.ru/tags/nft.md"
  - name: "solidity"
    url: "https://metalamp.ru/tags/solidity.md"
---

# Понимание стейкинга NFT: механизмы и преимущества

![Понимание стейкинга NFT: механизмы и преимущества](https://metalamp.ru/images/stejking_nft.png)

Стейкинг NFT — способ заблокировать активы (NFTs) на смарт-контрактах с возможностью получения вознаграждения или других привилегий протокола без необходимости продажи или передачи NFT другому пользователю.

 Множество проектов ищут способы принести пользу владельцам NFT и пытаются стимулировать долгосрочное владение или вовлечение в экосистему проекта. Для таких проектов стейкинг NFT может стать хорошим инструментом привлечения новых участников. А для держателей NFT способом получать пассивный доход.

 
## Как работает стейкинг NFT?

 Обычно стейкинг NFT требует реализации следующих смарт-контрактов:

 **1.** **Смарт-контракт NFT.** Предмет стейкинга. По сути актив для стейкинга.

 **2. Смарт-контракт Staking.** Хранилище смарт-контрактов NFT.

 **3. Смарт-контракт для вознаграждений (rewards).** Это может быть токен любого стандарта: ERC-20, ERC-721, ERC-1155.

 Для того, чтобы застейкать **NFT** владелец должен передать ее во владение смарт-контракту **Staking**. После этого ему будет доступно вознаграждение согласно правилам, описанным на смарт-контракте **Staking**. Например, за одну «застейканную» **NFT** сроком на один год, пользователь получит ERC-20 токен эквивалентный 100$.

 ![](https://metalamp.ru/images/58531111234df5116709bebbcf76bf0d.png)

 Вознаграждение или привилегии являются стимулом, который должен привлекать владельцев «стейкать» NFT за место обычного владения.

 Реализация частных случаев может предоставлять off-chain привилегии за место вознаграждения в токене. Например, пропуск на блокчейн конференцию. Это означает, что смарт-контракт **Rewards** может отсутствовать в данной схеме. В этой схеме возможны различные вариации на любой вкус.

 
## Как рассчитывается вознаграждение за стейкинг?

 Когда вознаграждение может быть измерено, то есть имеет количественную характеристику, например, сумма в ERC-20 токене, необходимо иметь прозрачную **систему** начисления вознаграждения.

 Подобная система может зависеть от нескольких факторов:

 
- **Продолжительность стейкинга.** Чем дольше NFT «застейкана», тем больше потенциальное вознаграждение.
- **Процентная ставка.** Различные платформы могут предлагать различные формы начисления процентов: фиксированную ставку, переменные ставки, ставки, зависящие от активности или выполнения специальных заданий и тому подобное.
- **Общая сумма стейкинга.** Общее количество «застейканных» NFT может влиять на размер вознаграждения.
- **Редкость или свойство NFT.** Различные показатели NFT могут влиять на размер вознаграждения. Например, редкая NFT может приносить вознаграждения больше, чем обычная.
- **Комиссия сети.** Оплата за газ для того, чтобы забрать вознаграждение может свести на нет любой пассивный доход.

 Например, формула может быть следующая:

 ![reward= \frac](https://habrastorage.org/getpro/habr/upload_files/a69/51d/122/a6951d12260992887b7ade5740b5610e.svg)

  
- **nftAmount** — количество застейканных NFT пользователем
- **annualRewardAmountPerNft **— количество вознаграждения за одну NFT, которая будет застейкана в течение одного года
- **stakingDuration **— реальное время, которое NFT была застейкана (в секундах)
- **secsPerYear** — количество секунд в году. Является константой и равняется `31556952` секундам. Это значение определяет 365.2425 дней, что является средней продолжительностью календарного года, согласно[грегорианскому календарю](https://en.wikipedia.org/wiki/Gregorian_calendar).

 Предположим, что пользователь застейкал одну NFT на 10 дней (864000 секунд). Годовое вознаграждение составляет 1000 ERC-20 токена.

 Тогда мы легко можем посчитать количество вознаграждения:

 ![reward = 1 * 1000 * 864000 / 31556952 ≈ 27.379 ](https://habrastorage.org/getpro/habr/upload_files/d8b/468/5a3/d8b4685a399e423024848e447e2ce5f6.svg)

 Двадцать семь (с копейками) токенов пользователь получит за 10 дней стейкинга согласно нашей модели. 

 
## Плюсы и минусы

 Как и все в этом мире, использование стейкинга NFT несет в себе плюсы и минусы. Подробности в сравнительной таблице ниже.

 ![](https://metalamp.ru/images/plus_minus.png)

 
## Чуть-чуть solidity кода

 В этом разделе я опишу четыре примера смарт-контракта, которые решают различные задачи стейкинга NFT.

 
### Самый простой стейкинг NFT

 Для реализации самого простого стейкинга NFT потребуется один смарт-контракт, назовем его SimpleStaking.sol, который будет регламентировать три основных процесса:

 
- передать NFT на контракт
- забрать NFT с контракта
- проверить, что контракт владеет NFT

 В момент передачи NFT контракту нужно записать реального владельца NFT, чтобы потом, только он, смог вернуть ее себе обратно. Для этого я объявил две переменных:

 
```
/// @notice Хранение адресов владельцев для застейканных NFT
mapping(uint256 tokenId => address stakeholder) private _stakes;

/// @notice Хранение количества застейканных NFT для каждого адреса
mapping(address stakeholder => uint256 counter) private _stakedNftBalance;```

 Чтобы передать NFT контракту, владелец должен вызвать функцию `stake()`.

 
```
function stake(uint256 tokenId) external {
    /// Перевод NFT от владельца контракту
    _nft.safeTransferFrom(msg.sender, address(this), tokenId);

    /// Запись данных о владельце
    _stakes[tokenId] = msg.sender;
    _stakedNftBalance[msg.sender] += 1;
}```

 *Важно!* Перед вызовом функции `stake()`, владельцу необходимо вызвать функцию `approve()` на контракте NFT и указать адрес контракта `SimpleStaking.sol`

 Чтобы забрать NFT обратно, владелец должен вызвать функцию `unstake()`.

 
```
function unstake(uint256 tokenId) external checkUnstake(tokenId) {
    /// Перевод NFT от контракта владельцу
    _nft.safeTransferFrom(address(this), msg.sender, tokenId);

    /// Удаление данных о владельце
    delete _stakes[tokenId];
    _stakedNftBalance[msg.sender] -= 1;
}```

 Подобная логика может позволить реализовать некоторые привилегии пользователям, которые застейкали NFT. Для этого необходимо убедиться, что NFT действительно застейкана через вызов функции `isStaked()`.

 
```
function isStaked(uint256 tokenId) external view returns (address) {
    return _stakes[tokenId];
}```

 Полный код контракта `SimpleStaking.sol` можно найти [тут](https://github.com/fullstack-development/blockchain-wiki/blob/main/concepts/nft-staking/contracts/src/SimpleStaking.sol).

 
### Стейкинг с возможностью сменить владельца NFT

 Задачи, которые решаются стейкингом NFT, могут быть гораздо сложнее. Например, неплохо было бы иметь возможность **передать право владения застейканной NFT** без необходимости забирать ее у смарт-контракта.

 Решать подобную задачу можно несколькими способами. Можно реализовать дополнительную функцию `transferOwnership(uint256 tokenId, address owner)` или пойти по пути [Uniswap](https://uniswap.org/), [Compound](https://compound.finance/) и других протоколов выдавая[lp токены](https://academy.binance.com/ru/articles/what-are-liquidity-pool-lp-tokens) для подтверждения права владения.

 Мне больше нравится второй вариант, потому что подобная механика дает больше гибкости. Поэтому реализуем его в смарт-контракте под названием `SimpleTransferableStaking.sol`.

 Для этого необходимо поменять реализации функций: `stake()` и `unstake()`.

 
```
function stake(uint256 tokenId) external {
    /// Перевод NFT от владельца контракту
    _nft.safeTransferFrom(msg.sender, address(this), tokenId);

    /// Выдается lp nft
    _lpNft.mint(msg.sender, tokenId);
}```

 
```
function unstake(uint256 tokenId) external checkUnstake(tokenId) {
    /// Перевод lp NFT от владельца контракту
    _lpNft.safeTransferFrom(msg.sender, address(this), tokenId);

    /// Перевод NFT от контракта владельцу
    _nft.safeTransferFrom(address(this), msg.sender, tokenId);

    /// Сжигается lp nft
    _lpNft.burn(tokenId);
}```

 Проверить, что NFT застейкана, можно вызвав функцию `ownerOf(tokenId)` на контракте lpNft (вернет адрес владельца) или `ownerOf(tokenId)` на контракте nft (вернет адрес контракта `SimpleTransferableStaking.sol`).

 Полный код контракта `SimpleTransferableStaking.sol` можно найти [тут](https://github.com/fullstack-development/blockchain-wiki/blob/main/concepts/nft-staking/contracts/src/SimpleTransferableStaking.sol).

 
### Стейкинг за вознаграждение

 Идем по нарастающей! Следующим примером стейкинга будет смарт-контракт, который позволит получать разовое вознаграждение за стейкинг NFT на определенный период в ERC-20 токене. Назовем такой смарт-контракт `StakingWithOneTimeReward.sol`.

 Принципиальное различие заключается в том, что вводятся следующие понятия:

 
- `rewardToken` - это токен, в котором будет выплачиваться вознаграждение
- `_stakeDuration` - это время на которое необходим застейкать NFT
- `_rewardAmountPerNft` - это сумма вознаграждение за одну NFT

 Снова дорабатываем функцию `stake() и unstake()`. Теперь важно хранить информацию о стейке NFT (время начала и продолжительность).

 
```
function stake(uint256 tokenId) external {
    /// Перевод NFT от владельца контракту
    _nft.safeTransferFrom(msg.sender, address(this), tokenId);

    /// Запись информации о стейке NFT
    _stakes[tokenId] = StakeInfo({
        owner: msg.sender,
        start: block.timestamp,
        duration: _stakeDuration
    });
}```

 Функция `unstake()` вызывает функцию `_claimReward()` для отправки токена вознаграждения.

 
```
function unstake(uint256 tokenId) external checkUnstake(tokenId) {
    /// Отправляет вознаграждение владельцу NFT
    _claimReward(msg.sender);

    /// Перевод NFT от контракта владельцу
    _nft.safeTransferFrom(address(this), msg.sender, tokenId);

    /// Удаление данных о стейке
    delete _stakes[tokenId];
}```

 
```
function _claimReward(address account) private {
    uint256 value = _rewardAmountPerNft;

    _rewardToken.safeTransfer(account, value);
}```

 Полный код контракта `StakingWithOneTimeReward.sol` можно найти [тут](https://github.com/fullstack-development/blockchain-wiki/blob/main/concepts/nft-staking/contracts/src/StakingWithOneTimeReward.sol).

 
### Стейкинг за годовое вознаграждение

 Последнее усложнение и последний пример смарт-контракта `StakingWithReusableReward.sol`. Позволим владельцу NFT получать за стейкинг NFT вознаграждение, но с возможностью забрать его в любой момент неограниченное количество раз.

 Установим на контракте сумму вознаграждения, которую владелец получит, если одна NFT будет застейкана ровно на один год `_annualRewardAmountPerNft`. От этой суммы будем вычислять вознаграждение для любого периода времени.

 Рассчитывать количество вознаграждения (актуализировать его сумму), которое ему причитается необходимо при любом взаимодеиствии владельца NFT со смарт-контрактом. За это будет отвечать модификатор `updateReward()` и приватная функция `_updateReward()`.

 
```
  modifier updateReward(address stakeholder) {
      _updateReward(stakeholder);

      _;
  }```

 Вешаем модификатор на функции stake(), unstake() и тогда будет сначала рассчитываться заработанное вознаграждение, а потом выполняться действие владельца NFT.

 
```
function stake(uint256 tokenId) external updateReward(msg.sender) {
    /// Перевод NFT от владельца контракту
    _nft.safeTransferFrom(msg.sender, address(this), tokenId);

    /// Сохранение информации о стейке NFT
    _stakes[tokenId] = msg.sender;

    /// Сохранение информации о накопление вознаграждения
    _stakerRewardInfo[msg.sender].tokenBalance += 1;
    _stakerRewardInfo[msg.sender].lastTimeRewardUpdated = block.timestamp;
}```

 
```
function unstake(uint256 tokenId) external updateReward(msg.sender) {
    _unstake(tokenId);
}```

 Расчет вознаграждения будет очень простым. Сумма вознаграждения рассчитывается за определенный период времени на базе суммы вознаграждения за один год "стейка" NFT.

 
```
function _calculateReward(uint256 nftAmount, uint256 startTime, uint256 endTime)
    private
    view
    returns (uint256 rewardAmount)
{
    /// Количество NFT * годовую сумму вознаграждения
    uint256 rewardPerYear = (nftAmount * _annualRewardAmountPerNft) / MULTIPLIER;

    /// Сумма за определенный период времени
    rewardAmount = (rewardPerYear * (endTime - startTime)) / SECS_PER_YEAR;
}```

 Полный код контракта `StakingWithReusableReward.sol` можно найти [тут](https://github.com/fullstack-development/blockchain-wiki/blob/main/concepts/nft-staking/contracts/src/StakingWithReusableReward.sol).

 
## Примеры приложений

 Несколько примеров проектов, которые сделали у себя стейкинг NFT в каком-то виде. Изучение опыта этих проектов может оказаться достаточно полезным для выбора и реализации собственной модели стейкинга NFT.

 
### Zookeeper

 [Zookeeper](https://www.zookeeper.finance/) — это геймефицированное децентрализованное приложение для обеспечения экосистемы ликвидностью. Для добычи ликвидности использует специальную ZooNFT. NFT можно заработать, купить или выиграть с помощью различных механизмов в приложении. Стейкинг этой NFT предоставляет уникальные возможности получения вознаграждения.

 
### MOBOX (MBOX)

 [MOBOX](https://www.mobox.io/home/#/) — это метавселенная, которая сочетает в себе фарминг и NFT. Позволяет стейкать NFT и получать вознаграждение в собственной валюте. NFT в рамках вселенной называются MOMO. Управляется сообществом. Команда стремиться достичь максимальной прозрачности, поэтому публикует [адреса](https://faqen.mobox.io/mobox-platform/smart-contracts) своих смарт-контрактов.

 
### Binance Fan Token Platform

 [Binance Fan Token Platform ](https://www.binance.com/en/fan-token?utm_source=fan-token)— уникальный сервис, который олицетворяет фанатскую базу популярных футбольных клубов, предоставляя пользователям возможность присоединится к своей любимой команде.

 Платформа реализовала под-сервис NFT PowerStation для «подзарядки NFTs», который позволяет получать вознаграждения в виде токенов фанатов Binance. Вознаграждение распределяется из заранее определенного пула токенов. Оно будет распределяться в зависимости от различных факторов: тип NFT, количества участников и так далее. Чем дольше NFT «заряжается», тем выше будут награды. Вознаграждение рассчитываются почасово.

 
## Вывод

 Стейкинг и NFT — две важные концепции в мире web3. Зачастую эти две технологии используются отдельно, однако стейкинг NFT позволяет переосмыслить использование NFT и создать новые варианты использования.

 
### Links

 1. [What Is NFT Staking and How Does It Work?](https://academy.binance.com/en/articles/what-is-nft-staking-and-how-does-it-work) 

 2. [NFT Staking Explained: Earning Passive Income with NFTs](https://coindcx.com/blog/cryptocurrency/nft-staking-explained/) 

 3. [Staking rewards](https://solidity-by-example.org/defi/staking-rewards/). Это не про NFT, но про получение вознаграждения.

 ![article-logo](https://metalamp.ru/images/article/logo.svg) 
## Больше статей о web3


## Custom Fields

**reading time:** 9

**Article type:** articles

**Article description:** Все знают про стейкинг обычных ERC-20 токенов. А что с ERC-721? Вокруг стейкинга NFT уже давно ходят противоречивые мнения, и мы решили разобраться в этом с точки зрения технологии. Поговорим о том, как это работает: как организовать на смарт-контрактах, как рассчитываются вознаграждения и какую пользу это может принести проектам и держателям NFT. Добро пожаловать в технический гид по стейкингу NFT!

**Editor's choice:** editors_choice

**Author (copy):** Павел Найданов

