Да, я знаю, многие другие руководства могут рассказать вам, как вы можете создать свою торговую площадку NFT с помощью Solidity и Polygon. Но сегодня я хочу показать вам мой туториал — где мы создадим нашу торговую площадку с помощью Solidity, Hardhat и Polygon 😎
Установка инструментов
Возможно, кто-то, кто читает эту статью, уже знает Solidity, но не знает, что такое Hardhat, и не знает, какие инструменты мы будем здесь использовать. Вы можете пропустить этот раздел, если вы уже установили Hardhat, и узнать, как использовать этот замечательный инструмент.
Для начала создадим новую папку с проектом с терминалом:
$ mkdir nft-marketplace $ cd nft-marketplace
Следующим шагом будет установка библиотеки Hardhat с помощью следующей команды:
$ npm install --save-dev hardhat
При запуске этой команды hardhat откроет меню установки нового проекта hardhat:
Теперь вам нужно выбрать здесь первый вариант и дождаться, когда будет создан новый проект каски. Когда генерация проекта будет завершена, пора приступать к разработке.
Приступаем к написанию договора
Теперь давайте откроем этот проект в Visual Studio Code, а также установим библиотеку OpenZeppelin в терминал:
$ npm install @openzeppelin/contracts
Что такое библиотека контрактов OpenZeppelin? Если мы хотим создавать быстрые токены NFT с нашим смарт-контрактом — нам нужно использовать эту библиотеку, потому что эта библиотека предоставляет все стандарты токенов. Но об этом мы поговорим подробнее в ближайшее время. Теперь переходим в папку «контракты», здесь вы видите контракт Greeter.sol — вы можете удалить этот контракт, так как он был создан по умолчанию. Теперь создайте новый файл с именем «NFT.sol» и откройте его. Во-первых, давайте определим идентификатор лицензии:
// SPDX-License-Identifier: MIT
Эта строка определяет лицензию нашего контракта, далее давайте определим версию Solidity, которую мы хотим использовать в нашем контракте:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4;
Я только что определил, что мы хотим использовать версию 0.8.4 в нашем контракте, теперь давайте определим тело контракта, а также импортируем стандарт токена ERC721 из библиотеки OpenZeppelin:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; contract FrogToken { // I named our contract - FrogToken // Here we gonna write all code of the contract }
Хорошо, теперь, когда мы импортировали стандарт токена ERC721 — пришло время использовать этот стандарт в нашем контракте следующим образом:
// ... contract FrogToken is ERC721 { // Here we gonna write all code of the contract }
Отлично, но это еще не все — теперь нам нужно создать конструктор и определить имя и символ нашего токена NFT:
// ... contract FrogToken is ERC721 { constructor() ERC721("FrogToken", "FT") { } }
Итак, теперь имя нашего токена будет FrogToken, а символ — FT. Отлично, следующий шаг — создать переменную с именем tokenCounter:
// ... contract FrogToken is ERC721 { uint public tokenCounter; constructor() ERC721("FrogToken", "FT") {} }
Почему мы создали эту переменную? Когда мы собираемся чеканить новый токен — нам нужно определить идентификатор токена — и здесь мы будем использовать значение этой переменной как идентификатор токена. Хорошо, следующий шаг — создать новую функцию монетного двора для токена:
// ... contract FrogToken is ERC721 { uint public tokenCounter; constructor() ERC721("FrogToken", "FT") {} function mintToken(string memory _tokenURI) public { } }
Зачем нам здесь нужна переменная _tokenURI? Когда мы собираемся чеканить новый токен — нам также нужно получить метаданные из URI токена, и именно поэтому мы добавили эту переменную здесь. Отлично, прежде чем мы напишем код для функции mintToken, нам нужно определить значение переменной tokenCounter равным нулю:
// ... contract FrogToken is ERC721 { uint public tokenCounter; constructor() ERC721("FrogToken", "FT") { tokenCounter = 0; } function mintToken(string memory _tokenURI) public {} }
Отлично, теперь давайте напишем функцию монетного двора:
// ... contract FrogToken is ERC721 { uint public tokenCounter; constructor() ERC721("FrogToken", "FT") { tokenCounter = 0; } function mintToken(string memory _tokenURI) public { _safeMint(msg.sender, tokenCounter); _setTokenURI(tokenCounter, msg.sender); tokenCounter++; } }
Теперь позвольте мне объяснить код этой функции. Во-первых, мы используем функцию _safeMint — для чеканки нового токена на адрес отправителя. И отправитель получит токен с идентификатором, равным текущему значению переменной tokenCounter.
После этого мы используем функцию _setTokenURI, где мы определяем URI токена токена, который имеет идентификатор переменной tokenCounter. Наконец, мы увеличиваем значение значения tokenCounter, а это означает, что в следующей чеканке — следующий отправитель получит токен с идентификатором, равным 1.
Напишите тест для нашего контракта
Теперь пришло время протестировать наш контракт и проверить, как теперь работает функция монетного двора. Итак, давайте перейдем в папку «tests» и создадим новый файл, который будет называться NftTest.js:
const { expect } = require("chai"); describe("Deploy contract and mint token", function() { let deployer; // owner of the contract let FrogToken; let frogToken; // here will be deployed contract beforeEach(async function () { [deployer] = await ethers.getSigners(); FrogToken = await ethers.getContractFactory("FrogToken"); frogToken = await FrogToken.deploy(); }); it("Test mint token", async function() { var tx = await frogToken.mintToken("http://my-json-server.typicode.com/abcoathup/samplenft/tokens/0"); await tx.wait(1); console.log("Here is you mint transaction: ", tx); }); });
Отлично, теперь давайте запустим следующую команду для проверки нашего контракта:
$ npx hardhat test
Если все будет хорошо — вы получите хэш транзакции в своем терминале. Итак, теперь мы можем создать простой контракт для чеканки токена NFT. Но это не все. Нам нужно создать сопоставление, которое сохранит все выпущенные токены некоторого пользователя. Итак, вернемся к нашему контракту NFT:
// ... contract FrogToken is ERC721 { // ... mapping(address => Token[]) public mintedTokens; struct Token { uint256 tokenId; string tokenURI; address payable owner; } // ... function getMintedToken(address _owner) public view returns(Token[]) { return mintedTokens[_owner]; } }
Что мы здесь видим? Во-первых, я создал сопоставление, которое будет сохранять все mintedTokens некоторых пользователей. Второе, что я создал — это структура Token, которую мы будем использовать для сохранения информации о выпущенных пользователем токенах. И последнее — это функция getMintedToken(), которая получает токены, отчеканенные пользователем. Теперь давайте перейдем к функции mintToken() и добавим некоторые изменения:
// ... function mintToken(string memory _tokenURI) public { _safeMint(msg.sender, tokenCounter); _setTokenURI(tokenCounter, msg.sender); mintedTokens[msg.sender] = Token(tokenCounter, _tokenURI, payable(msg.sender)); tokenCounter++; } // ...
Как видите, мы только что добавили небольшой фрагмент кода, который добавит отчеканенные токены в сопоставление mintedTokens(). Это круто — потому что теперь мы можем получить информацию о том, какие токены чеканили те или иные пользователи. Прохладный! Теперь напишем еще один тест, откроем NftTest.js:
const { expect } = require("chai"); describe("Deploy contract and mint token", function() { let deployer; // owner of the contract let FrogToken; let frogToken; // here will be deployed contract beforeEach(async function () { [deployer] = await ethers.getSigners(); FrogToken = await ethers.getContractFactory("FrogToken"); frogToken = await FrogToken.deploy(); }); it("Test mint token", async function() { var tx = await frogToken.mintToken("http://my-json-server.typicode.com/abcoathup/samplenft/tokens/0"); await tx.wait(1); console.log("Here is you mint transaction: ", tx); }); it("Test getMintedToken()", async function() { var token = await frogToken.getMintedToken(msg.sender); console.log( token ); }); });
Хорошо, теперь давайте запустим этот тест, и посмотрим результат, что мы получим токен. Итак, давайте сделаем это:
$ npx hardhat test
И что мы видим в терминале? Мы видим результат нашего теста, это хэш транзакции и объект токена. Но проверять результат мы не утверждали, пока можно сказать, что проверяли только функцию «getMintedToken». Это хорошо, и следующее, что нам нужно сделать — это написать скрипт для развертывания нашего контракта в тестовой сети (например, Rinkeby).
Развертывание в сети Rinkeby
Прежде чем мы развернем наш контракт в сети Rinkeby, вам нужно иметь расширение кошелька metamask (но я думаю, что у людей, которые уже читают эту статью, это расширение уже есть и они знают, как им пользоваться). А также вам нужно будет создать кошелек и переключиться на тестовую сеть с именем rinkeby:
И вы будете делать все эти шаги, вам нужно будет получить немного тестового эфира на кран ринкеби. Инструкции по получению тестового эфира вы найдете на странице сборщика rinkeby. А теперь вам нужно создать учетную запись Алхимия. Почему? Нам это понадобится, когда мы собираемся развернуть контракт, поэтому создайте учетную запись, а после этого — вам нужно будет создать новое приложение:
И когда вы будете создавать новое приложение — важно выбрать, что мы хотим использовать сеть Rinkeby:
Отлично, теперь давайте откроем ключи проекта, нажав на кнопку «Просмотреть ключи». Здесь вам нужно будет скопировать URL-адрес приложения:
Скопируйте URL-адрес вашего приложения, и нам нужно будет использовать его в конфигурации HardHat. Итак, теперь давайте откроем наш редактор кода и откроем файл hardhat.config.js:
/** * @type import('hardhat/config').HardhatUserConfig */ module.exports = { solidity: "0.8.4", networks: { rinkeby: { url: 'your-app-url', accounts ["your-wallet-secret"] } }, };
Если вы не знаете, где взять секретный ключ кошелька метамаски — вот небольшая инструкция. Сначала откройте данные своего кошелька:
Откроется окно, в котором вы можете получить секретный ключ своего кошелька после ввода пароля своей учетной записи:
Скопируйте секретный ключ и вставьте в файл конфигурации. Теперь у нас есть все, что нужно для развертывания нашего контракта, поэтому давайте перейдем в папку scripts и создадим новый файл с именем deploy.js:
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("FrogToken"); const token = await Token.deploy(); console.log("Token address:", token.address); } main() .then(() => process.exit(0)) .catch((error) => { console.error(error); process.exit(1); });
Хорошо сделано. Теперь давайте откроем терминал и запустим следующую команду для развертывания этого контракта:
$ npx hardhat run scripts/deploy.js --network rinkeby
В терминале вы увидите следующее:
Если вы скопируете адрес токена и зайдете на rinkeby etherscan — вы обнаружите, что ваш контракт развернут:
Потрясающе 💥 Это действительно отличный результат. В следующей части этой статьи — мы напишем первую версию нашего приложения — куда мы добавим наши токены и покажем их. Скоро мы также обновим код контракта и сделаем его более динамичным с большим количеством функций. На этом все — спасибо за прочтение 😎 и до встречи в следующей части статьи