Учебник по написанию смарт-контракта
Данная статья является большим и подробным учебником по написанию смарт-контрактов. Вы можете начать читать его, не обладая особыми познаниями, однако скорее всего вы мало что поймете
Если оформление статьи вам кажется ужасным, то включите белую тему в настройках Телетайпа
Ресурсы для новичков:
1. НАСТРОЙКА ОКРУЖЕНИЯ
🦊 Метамаск
Перед выводом СК в майнет (официальный запуск) нам нужно будет его написать и протестировать. Тестирование требует проведения транзакций теми монетами, которыми мы пользуемся в обычной нашей жизни — ETH
Разумеется никто не будет тратить настоящие деньги для простой проверки минта или апрува абстрактного NFT, поэтому были созданы тестовые сети, которые дают вам определенное количество "расходного материала"
✔ Rinkeby — дает тестовый ETH для работы в его же тестовой сети. Есть ограничения на получение и требуется привязать социальные сети (Твиттер или Фейсбук) чтобы никто слишком не абузил кран
Перед началом нам надо добавить тестовые сети в нашем кошельке Метамаск
🚿 Получаем тестовый ETH
1) Создаем кошелек Метамаск (можете использовать нынешний)
2) Открываем виджет Метамаска, нажимаем на кружочек идентикона. Снизу заходим в "Настройки"
3) Во вкладке "Дополнительно" активируем кнопку "Показывать тестовые сети"
4) Теперь мы можем выбрать тестовую сеть Rinkeby и перейти на сайт, где получим ETH
💻 Создание директории и установка пакетов
Следующие команды будут выполнятся на системе под Линуксом. Если вы не умеете пользоваться командной строкой, то пройдите этот курс
sudo apt install npm
2) Создаем директорию для проекта
mkdir nft-tutorial cd nft-tutorial
3) Инициализируем проект и заполняем описание и прочее. Можете просто прокликать Enter
npm init
npm install --save-dev hardhat ... npm install --save-dev @nomiclabs/hardhat-ethers ... npm install --save-dev @nomiclabs/hardhat-etherscan ... npm install @openzeppelin/contracts ... npm install dotenv --save ... npm install --save-dev ethers@^5.0.0 ... npm install --save-dev node-fetch@2 ...
5) После всей установки у вас будет такое содержание файла (если чуть отличается - ничего страшного)
{ "name": "asynco", "version": "1.0.0", "description": "Test", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Asynco", "license": "ISC", "devDependencies": { "@nomiclabs/hardhat-ethers": "^2.0.2", "ethers": "^5.5.2", "hardhat": "^2.7.0", "node-fetch": "^2.6.6" }, "dependencies": { "@openzeppelin/contracts": "^4.4.0", "dotenv": "^10.0.0" } }
👷♂️ Инициализация жесткого диска
Теперь нам надо донастроить жесткий диск при помощи Hardhat.
> npx hardhat 888 888 888 888 888 888 888 888 888 888 888 888 888 888 888 8888888888 8888b. 888d888 .d88888 88888b. 8888b. 888888 888 888 "88b 888P" d88" 888 888 "88b "88b 888 888 888 .d888888 888 888 888 888 888 .d888888 888 888 888 888 888 888 Y88b 888 888 888 888 888 Y88b. 888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888 👷 Welcome to Hardhat v2.7.0 👷 ? What do you want to do? … Create a basic sample project Create an advanced sample project Create an advanced sample project that uses TypeScript ❯ Create an empty hardhat.config.js Quit ... ✨ Config file created ✨
2) В конце у нас будет такая структура файла:
> tree -L 3 -I 'node_modules*|cache*' . ├── hardhat.config.js ├── package-lock.json └── package.json
2. БАЗОВЫЙ ПРОЕКТ
🔗 Структура каталогов
Начнем с организации нашей структуры. Будет две папки, в одной сам контракт, в другом будет сценарий для деплоя
mkdir contracts mkdir scripts
📃 Шаблон контракта
Заходим в нашу папку contracts/
и создаем там первый файл NFT.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/utils/Counters.sol"; contract NFT is ERC721 { using Counters for Counters.Counter; Counters.Counter private currentTokenId; constructor() ERC721("NFTTutorial", "NFT") {} function mintTo(address recipient) public returns (uint256) { currentTokenId.increment(); uint256 newItemId = currentTokenId.current(); _safeMint(recipient, newItemId); return newItemId; } }
Теперь разберемся, что тут написано:
ERC721.sol
: "ванильная" реализация для NFT уже реализует массу полезных вспомогательных функций. Для получения дополнительной информации обратитесь к документации OpenZeppelin;Counters.sol
: предоставляет счетчики, которые мы можем использовать для отслеживания общего количества отчеканенных токенов;
- следующие несколько строк определяют сам контракт NFT;
- строка 7 определяет NFT для наследования от ERC721. Обратите внимание, что контракты Solidity поддерживают использование миксинов и могут наследовать сразу несколько разных контрактов;
- Строки 8-9 импортируют, а затем объявляют счетчик, который мы будем использовать для эффективного отслеживания общего количества токенов, отчеканенных в нашем контракте;
- Строка 11 определяет конструктор, который на данный момент просто вызывает свой родительский конструктор ERC721 и передает две строки:
name
иsymbol
. На данный момент этот конструктор довольно легкий; - Наконец, мы определяем
mintTo
функцию. Эта публичная функция может быть вызвана путем передачи действительного адреса получателя для чеканки нового NFT. На данный момент это очень просто: - Это увеличивает наш
currentTokenId
счетчик; - Минтит следующее значение счетчика
recipient
, используя_safeMint()
(частный метод OpenZeppelin); - Наконец, он возвращает идентификатор новоиспеченного токена обратно вызывающему адресу.
🎉 Поздравляю!
Этот очень простой контракт - почти все, что вам нужно написать, прежде чем мы сможем его развернуть и использовать.
Хотя сейчас нам не хватает многих важных функций, которые делают NFT полноценными, эта базовая реализация может быть развернута и использована на OpenSea или любом другом рынке NFT.
Благодаря стандартизированной реализации OpenZeppelin, контракты получаются простые и красивые
После всех манипуляций мы получаем следующую структуру нашего проекта:
tree -L 3 -I 'node_modules*|cache*' ├── contracts │ └── NFT.sol ├── hardhat.config.js ├── package-lock.json ├── package.json └── scripts
🔩 Компиляция контракта
Теперь, когда мы написали наш (базовый) контракт, давайте поработаем над его компиляцией и убедимся, что он готов к развертыванию.
Для этого нам придется интегрировать нашу ранее созданную учетную запись MetaMask в наш проект, так как развертывание контракта будет стоить нам ETH.
1) Давайте создадим новый файл, .env
в корневой папке проекта. Мы будем использовать этот файл для хранения закрытого ключа нашей учетной записи, а также нашего API-ключа Alchemy.
- Получите свой закрытый ключ из кошелька MetaMask, следуя инструкциям здесь
- Получите свой API-ключ Alchemy здесь
2) После добавим в созданный выше файл наши данные по следующей структуре:
ALCHEMY_KEY = "alchemy-api-key" ACCOUNT_PRIVATE_KEY = "private-key"
3) Затем обновите hardhat.config.js
файл, добавив следующее:
/** * @type import('hardhat/config').HardhatUserConfig */ require('dotenv').config(); require("@nomiclabs/hardhat-ethers"); const { ALCHEMY_KEY, ACCOUNT_PRIVATE_KEY } = process.env; module.exports = { solidity: "0.8.0", defaultNetwork: "rinkeby", networks: { hardhat: {}, rinkeby: { url: `https://eth-rinkeby.alchemyapi.io/v2/${ALCHEMY_KEY}`, accounts: [`0x${ACCOUNT_PRIVATE_KEY}`] }, ethereum: { chainId: 1, url: `https://eth-mainnet.alchemyapi.io/v2/${ALCHEMY_KEY}`, accounts: [`0x${ACCOUNT_PRIVATE_KEY}`] }, }, }
npx hardhat compile
Мы должны получить следующий ответ:
Compiling 1 file with 0.8.0 Compilation finished successfully
📚 Развертывание контракта
Теперь мы готовы развернуть контракт. Напишем для этого простой скрипт на JS
1) Внутри scripts/
создайте файл с именем deploy.js
и заполните его таким кодом:
async function main() { // Get our account (as deployer) to verify that a minimum wallet balance is available const [deployer] = await ethers.getSigners(); console.log(`Deploying contracts with the account: ${deployer.address}`); console.log(`Account balance: ${(await deployer.getBalance()).toString()}`); // Fetch the compiled contract using ethers.js const NFT = await ethers.getContractFactory("NFT"); // calling deploy() will return an async Promise that we can await on const nft = await NFT.deploy(); console.log(`Contract deployed to address: ${nft.address}`); } main() .then(() => process.exit(0)) .catch((error) => { console.error(error); process.exit(1); });
- Эта функция JS извлекает скомпилированный контракт
NFT.sol
и развертывает его. Он также распечатывает баланс вашего кошелька до и после развертывания, так как в транзакции будет использоваться газ. - Большая часть этого кода скопирована из Hardhat, которые отлично справляются с объяснением различных компонентов в их учебнике. Не позволяйте названию раздела обмануть вас, он полон полезной информации Hardhat, связанной не только с тестированием.
2) Теперь наш проект должен выглядеть так:
> tree -L 3 -I 'node_modules*|cache*' ├── artifacts │ ├── @openzeppelin │ │ └── contracts │ ├── build-info │ │ └── f179c78b6322d2fddc1e72511467aa46.json │ └── contracts │ └── NFT.sol ├── contracts │ └── NFT.sol ├── hardhat.config.js ├── package-lock.json ├── package.json └── scripts └── deploy.js 8 directories, 6 files
Примечание: artifactsr
содержит все наши скомпилированные контракты и их зависимости
npx hardhat run scripts/deploy.js --network rinkeby
и получаем ошибку такой ответ:
Deploying contracts with the account: {адрес контракта} Account balance: 505059205368653101 Contract deployed to address: {адрес NFT контракта}
🛂 Проверяем контракт
Из ответа выше берем адрес развернутого контракта и смотрим в Etherscan Rinkeby результаты! Ура, мы программисты!
Поскольку мы потратили время , чтобы определить как тестовую сеть Rinkeby, так и основную сеть Ethereum в качестве сетевых опций hardhat.config.js
, развертывание одного и того же кода контракта в основной сети (когда мы будем готовы) так же просто, как замена --network rinkeby--network ethereum
в приведенной выше команде =)
3. Итоги
- Мы написали базовый смарт-контракт в Solidity, который использует существующую
ERC721
реализацию - Мы создали базовый конфигурационный файл Hardhat и скомпилировали наш первый контракт
- Наконец, мы написали базовый скрипт для развертывания нашего контракта на JavaScript и фактически развернули его в Rinkeby блокчейне
Но это только самое начало, пока контракт ничего толкового не делает. Это мы исправим в следующих частях учебника (если я опять не забью на канал на недельку)
✅ В следующей части рассмотрим:
- как чеканить токены, используя некоторые новые скрипты
- как мы можем улучшить наш контракт, чтобы дать ему более продвинутую функциональность, такую как цены покупки mint
- как добавить некоторые аккуратные метаданные, чтобы он действительно показывал ваше удивительное искусство, когда пользователи просматривают его на OpenSea.
Канал - https://t.me/in_crypto_Info
Дропы - https://t.me/in_airdrop_crypto