Пошаговая инструкция как создать NFT коллекцию на Golang + Ethereum (часть 3)

от автора

Привет, Хабр. 

В прошлых статьях мы научились генерировать 10 000 изображений для нашей NFT коллекции с помощью Golang, а также загрузили все сгенерированные изображения в децентрализованное хранилище IPFS.

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

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

Давайте подробней рассмотрим, какие методы должны быть у нашего смарт-контракта:

    function balanceOf(address _owner) external view returns (uint256);     function ownerOf(uint256 _tokenId) external view returns (address);     function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;     function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;     function transferFrom(address _from, address _to, uint256 _tokenId) external payable;     function approve(address _approved, uint256 _tokenId) external payable;     function setApprovalForAll(address _operator, bool _approved) external;     function getApproved(uint256 _tokenId) external view returns (address);     function isApprovedForAll(address _owner, address _operator) external view returns (bool); 

Полную спецификацию по ERC-721 стандарту можно посмотреть тут.

Нам не обязательно реализовывать весь стандарт самим, более оптимальным подходом является переиспользовать готовые библиотеки: OpenZeppelin — это библиотека для разработки безопасных смарт-контрактов и именно с ней мы будем работать.

Плюсы такого решения очевидны:

  • Готовый код из под коробки, прошедший аудит безопасности

  • Аудит нашего смарт-контракта займет гораздо меньше денег и времени

Создание смарт-контракта

Давайте создадим типовой смарт-контракт для нашей NFT коллекции, назовем её MonkeyNFT. Наш смарт-контракт наследует стандартные OpenZeppelin библиотеки, а именно:

  • ERC721 / ERC721Enumerable — контрактные модули, которые обеспечивают базовые функциии для нашего NFT токена

  • Ownable — контрактный модуль, который обеспечивает базовый механизм контроля доступа

Разрабатывать смарт-контракт, мы будем с помощью такого инструмента, как hardhat, очень крутой инструмент для разработки, тестирования и деплоя, особенно для тех, кто устал от Truffle и его бесконечного количества багов.

Запускаем команду npx hardhat init, для создания нового шаблона для нашего смарт-контракта, далее переходим в директорию contracts и пишем код:

// SPDX-License-Identifier: MIT pragma solidity ^0.8.0;  import "@openzeppelin/contracts/utils/Context.sol"; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/math/SafeMath.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";  contract MonkeyNFT is ERC721, ERC721Enumerable, Ownable {      using SafeMath for uint256;     uint public constant maxPurchase = 10;     uint256 public constant MAX_MONKEYS = 10000;      uint256 private _monkeyPrice = 80000000000000000; //0.08 ETH     string private baseURI;     bool public saleIsActive = true;      constructor() ERC721("The Monkey NFT", "MNK") {     }          function _beforeTokenTransfer(address from, address to, uint256 tokenId)         internal         override(ERC721, ERC721Enumerable)     {         super._beforeTokenTransfer(from, to, tokenId);     }          function supportsInterface(bytes4 interfaceId)         public         view         override(ERC721, ERC721Enumerable)         returns (bool)     {         return super.supportsInterface(interfaceId);     }  function withdraw() public onlyOwner { uint256 balance = address(this).balance; payable(msg.sender).transfer(balance); }          function setPrice(uint256 _newPrice) public onlyOwner() {         _monkeyPrice = _newPrice;     }      function getPrice() public view returns (uint256){         return _monkeyPrice;     }      function mintMonkeys(uint numberOfTokens) public payable {         require(saleIsActive, "Sale must be active to mint Monkeys");         require(numberOfTokens <= maxPurchase, "Can only mint 10 tokens at a time");         require(totalSupply().add(numberOfTokens) <= MAX_MONKEYS, "Purchase would exceed max supply of Monkeys");         require(_monkeyPrice.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");                  for(uint i = 0; i < numberOfTokens; i++) {             uint mintIndex = totalSupply();             if (totalSupply() < MAX_MONKEYS) {                 _safeMint(msg.sender, mintIndex);             }         }     }            function _baseURI() internal view override returns (string memory) {         return baseURI;     }          function setBaseURI(string memory newBaseURI) public onlyOwner {         baseURI = newBaseURI;     }      function flipSaleState() public onlyOwner {         saleIsActive = !saleIsActive;     }   }  

Из интересного:

  • Флаг saleIsActive — говорит, о том, что наша коллекция либо готова к продаже, либо нет. Данная функция очень полезна на начальном этапе, когда по каким то причинам необходимо остановить продажи.

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

  • Функция mintMonkey — это основная функция, через которую пользователи могут купить наш NFT токен. Модификатор payable, как раз говорит о том, что вызов данной функции для пользователя платный.

Для того, чтобы скомпилировать наш смарт-контракт, запускаем команду npx hardhat compile, после чего создадутся abi-артифакты в директории artifacts.

Тестирование смарт-контракта

Т.к. загруженный смарт-контракт нельзя модифицировать, нам обязательно необходимо его протестировать до деплоя в блокчейн Ethereum. Переходим в директорию test, удаляем дефолтные тесты и пишем свои:

const { expect } = require("chai");  describe("Token contract", () => {    let contract;   let owner;   let addr1;   let addr2;   let addrs;   let baseURI;    beforeEach(async () => {     const Token = await ethers.getContractFactory("MonkeyNFT");     [owner, addr1, addr2, ...addrs] = await ethers.getSigners();     contract = await Token.deploy();     baseURI = "https://hardhat.org/test/"     await contract.setBaseURI(baseURI)   });    it("Should initialize contract", async () => {     expect(await contract.MAX_MONKEYS()).to.equal(10000);   });   it("Should set the right owner", async () => {     expect(await contract.owner()).to.equal(await owner.address);   });   it("Should mint", async () => {     const price = await contract.getPrice();     const tokenId = await contract.totalSupply();     expect(       await contract.mintMonkeys(1, {         value: price,       })     ).to.emit(contract, "Transfer").withArgs(ethers.constants.AddressZero, owner.address, tokenId);       expect(await contract.tokenURI(tokenId)).to.equal(baseURI+"0");   }); });

Запускаем тестирование смарт-контракта, командой npx hardhat test. Если все тесты пройдены, то мы увидем:

Compiling 1 file with 0.8.4 Compilation finished successfully     Token contract     ✓ Should initialize contract     ✓ Should set the right owner     ✓ Should mint (41ms)     3 passing (1s)

Только, что мы успешно протестировали:

  • Создание смарт-контракта

  • Успешно установили владельца смарт-контракта

  • Успешно вызвали платную функцию mintMonkeys для продажи NFT токена

Деплой смарт-контракта

Деплоить мы будем в тестовую сеть Ethereum. Переходим в директорию scripts и удаляем дефолтные скрипты, они нам больше не понадобяться и пишем новый скрипт деплоя:

async function main() {     const [deployer] = await ethers.getSigners();        console.log("Deploying contracts with the account:", deployer.address);        console.log("Account balance:", (await deployer.getBalance()).toString());        const Token = await ethers.getContractFactory("MonkeyNFT");      console.log("Deploying contract...");     const token = await Token.deploy();     await token.deployed();     console.log("Token address:", token.address);   }      main()     .then(() => process.exit(0))     .catch((error) => {       console.error(error);       process.exit(1);     }); 

Так же нам необходимо изменить файл hardhat.config следующим образом:

require("@nomiclabs/hardhat-waffle"); require("@nomiclabs/hardhat-etherscan");  module.exports = {   solidity: "0.8.4",   networks: {     rinkeby: {       url: `https://eth-rinkeby.alchemyapi.io/v2/${YOUR_API_KEY}`,     },   }, };

Отлично все готово для деплоя, запускаем скрипт: npx hardhat run scripts/deploy.js --network rinkeby

В следующей статье я расскажу как взаимодействовать с нашим созданным смарт-контрактом с помощью web3.js.


ссылка на оригинал статьи https://habr.com/ru/articles/596343/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *