July 7, 2022

Lazy minting

Минт NFT в большинстве блокчейнов обычно требует трат на газ. Многим до сих пор снятся эфир минты с комсой по пару k$.

Однако используя несколько методов, можно платить газ за минт в момент первой продажи. Плата происходит только в тот момент, когда нфт присваивается юзером, создателю же платить не придется, просто из цены продажи вычитается часть для оплаты газа

Минт "точно в срок" в момент покупки называют lazy mint, и данный метод был принят такими торговыми площадками, как OpenSea , чтобы снизить барьер для входа создателям NFT, позволяя создавать NFT без каких-либо предварительных затрат.(Многие уже создавали свои коллекции на ОС, не платив комиссии. Если вы этого не делали то попробуйте здесь )

В этой статье будет показан пример на Ethereum с использованием некоторых вспомогательных библиотек и базовых контрактов от OpenZeppelin .

Как это работает

Основная предпосылка ленивого минта заключается в том, что вместо непосредственного создания NFT путем вызова контрактной функции создатель NFT готовит подпись некоторых данных, используя приват кей.

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

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

В нашем примере мы используем структуру Solidity для представления нашего ваучера:

struct NFTVoucher {
  uint256 tokenId;
  uint256 minPrice;
  string uri;
  bytes signature;
}

Создание подписанного ваучера

Использование подписей для авторизации может быть сложным, поскольку третья сторона потенциально может взять данные, которые были подписаны в одном контексте, и представить их где-то в другом месте. Как пример, они могут взять подпись, разрешающую создание NFT в тестовой сети Ropsten, и представить ее контракту, развернутому в mainnet. Если подписываемые данные не содержат какой-либо контекстной информации, этот вид "повторной атаки" довольно тривиален для выполнения и от него трудно защититься.

Чтобы устранить эти проблемы, а также обеспечить лучший пользовательский опыт при подписании сообщений, сообщество Ethereum разработало EIP-712, стандарт для подписи типизированных структурированных данных. Подписи, созданные с помощью EIP-712, "привязаны" к определенному экземпляру смарт-контракта, работающего в определенной сети. Они также содержат информацию о типе, так что такие инструменты, как MetaMask, могут предоставлять пользователю более подробную информацию о подписываемых данных вместо непрозрачной строки шестнадцатеричных символов. Однако стандарт является отличной защитой лишь он-чейн, возможность перехвата данных во время их поступления в дапп все так же остается. (Пример, когда использовали кошельки находящиеся в вайтлисте, передавали его на бек проекта и в ответ получали данные для подписи транкзации).

Мы используем класс JavaScript под названием LazyMinter для подготовки подписанных ваучеров с использованием EIP-712. Поскольку подписи привязаны к конкретному контракту, вам необходимо указать адрес развернутого контракта и ethers.js:

const lazyminter = new LazyMinter({ myDeployedContract.address, signerForMinterAccount })
 async createVoucher(tokenId, uri, minPrice = 0) {
    const voucher = { tokenId, uri, minPrice }
    const domain = await this._signingDomain()
    const types = {
      NFTVoucher: [
        {name: "tokenId", type: "uint256"},
        {name: "minPrice", type: "uint256"},
        {name: "uri", type: "string"},  
      ]
    }
    const signature = await this.signer._signTypedData(domain, types, voucher)
    return {
      ...voucher,
      signature,
    }
  }

Сначала мы готовим наш неподписанный объект ваучера и получаем домен подписи. Объект types содержит информацию о типе для полей нашего NFTVoucher (исключая саму подпись).

Чтобы создать подпись, мы вызываем метод _signTypedData для нашего объекта Signer, передавая домен, определение типа и неподписанный объект ваучера.

Наконец, мы возвращаем полный объект ваучера с включенной подписью, который можно использовать в нашем смарт-контракте.

Оплата газа при покупке

Чтобы метод работал, нам нужна функция смарт-контракта, которую может вызвать покупатель NFT, чтобы заминтить за одну транзакцию:

 function redeem(address redeemer, NFTVoucher calldata voucher) public payable returns (uint256) {
    // make sure signature is valid and get the address of the signer
    address signer = _verify(voucher);

    // make sure that the signer is authorized to mint NFTs
    require(hasRole(MINTER_ROLE, signer), "Signature invalid or unauthorized");

    // make sure that the redeemer is paying enough to cover the buyer's cost
    require(msg.value >= voucher.minPrice, "Insufficient funds to redeem");

    // first assign the token to the signer, to establish provenance on-chain
    _mint(signer, voucher.tokenId);
    _setTokenURI(voucher.tokenId, voucher.uri);
    
    // transfer the token to the redeemer
    _transfer(signer, redeemer, voucher.tokenId);

    // record payment to signer's withdrawal balance
    pendingWithdrawals[signer] += msg.value;

    return voucher.tokenId;
  }

Сначала мы вызываем вспомогательную функцию _verify, которая либо возвращает адрес учетной записи, подготовившей подпись, либо отменяет транзакцию, если подпись недействительна.

Как только у нас есть адрес подписавшего, мы проверяем, что он авторизован для минта с помощью функции hasRole из контракта AccessControl на основе ролей OpenZeppelin.

Мы также следим за тем, чтобы покупатель отправил достаточно ETH, чтобы покрыть минимальную цену. Если это так, мы можем создать новый токен на основе информации в ваучере и перевести его на учетную запись получателя.

Наконец, мы помещаем платеж в сопоставление под названием pendingWithdrawals, чтобы создатель NFT мог позже вывести свой ETH.

Заключение

Данный способ позволяет экономить на газе, однако еще мало используем среди коллеций на евм блокчейне. Минусом данного метода является возможность для злоумышленников создавать фейк или фишинг коллекции, которые многие замечали на своих аккаунтах Opensea в разделе hidden(крайне НЕ РЕКОМЕНДУЮ взаимодействовать с данными объектами).

Первоисточник: тык