Создание NFT-коллекции для новичков
DISCLAIMER: Сразу оговоримся, что в статье используется много упрощений и ручной работы, для профессионального использования материал не подходит. Код можно усовершенствовать, а некоторые процессы автоматизировать. Однако нашей целью стало подготовить исчерпывающий гайд по запуску генеративной коллекции для новичков на русском языке.
В сети есть примеры и куски кода, но отсутствуют простые материалы на русском, поэтому мы решили поделиться тем, как запускали свою коллекцию и привести готовые скрипты с комментариями для абсолютных новичков.
Что нам потребуется?
Первым делом мы определимся с блокчейном для запуска коллекции и установим необходимое ПО.
Исторически именно блокчейн Polygon используется как дешёвая альтернатива Ethereum. Сама сеть является сайд-чейном сети эфира, то есть имеет полную совместимость и целую пачку межсетевых шлюзов, а все топовые маркетплейсы его поддерживают в полной мере. Для нашей задачи подходит идеально, если у вас другие цели, то нужно будет необходимо поменять пару строчек в коде, отметим это дальше по тексту.
Чтобы задеплоить контракт мы будем использовать онлайн версию среды разработки Remix, поэтому установка ПО для этого не потребуется. Но чтобы заминтить токены по заранее сформированному списку получателей нам понадобится использовать какое-либо ПО — под рукой оказался Python, да и мода на него позволяет предположить, что с основами языка знакомы даже неискушённые пользователи.
Мы использовали бесплатную версию среды разработки PyCharm с предустановленной версией языка 3.11.
Забегая вперёд, по необъяснимым причинам библиотека web3 не смогла корректно установиться (вероятно, были необходимы дополнительные библиотеки для корректной установки), но времени и желания разбираться в причинах возникновения ошибок не было, поэтому попытка повторилась для версии 3.9 (которая оставалась от предыдущих проектов), где всё прошло как по маслу. Поэтому имейте в виду, код должен работать в обеих версиях, но если не получится установить библиотеку для интерпретатора версии 3.11, то попробуйте скачать более старую версию.
Для хранения данных используем бесплатный IPFS-хостинг: FILEBASE
И последнее, что нам потребуется, это браузерное расширение MetaMask с зарегистрированным кошельком.
Генерация метаданных для NFT
Чтобы коллекция была каноничной, метаданные будут храниться децентрализованно, поэтому первое, что мы делаем, это заранее генерируем изображения и необходимые файлы с описанием.
В нашем случае было использовано единое изображение для всех токенов, поэтому опустим процесс генерирования готовых изображений из слоёв (при необходимости примеры и подсказки можно найти в сети).
А вот простейший пример кода для генерирования пачки необходимых файлов с метаданными:
<?php
$nums=555; $i=0;
while ($i <= $nums) { file_put_contents(str_pad($i, 3, "0", STR_PAD_LEFT).'.json', '{
"name": "Unit #'.str_pad($i++, 3, "0", STR_PAD_LEFT).'",
"description": "The award for loyal BestChange fans who have actively interacted with the service.",
"image": "ipfs://QmYBrWr1MCMCxNB78RF96EzJHypZ658nUGtAQYq8VnWfqj"
}');}
?>
В нашем примере генерируются файлы 000.json, 001.json и так далее до 555.json. В поле name имя токена, содержащее его порядковый номер, description содержит описание, которое размещено непосредственно под изображением на маркетплейсах, а поле image содержит ссылку на изображение. В нашем случае это ссылка на децентрализованное хранилище, поэтому адрес начинается с ipfs://
, хотя подойдут ссылки и на централизованные сервера.
Содержимое файлов будет примерно таким:
{
"name": "Unit #002",
"description": "The award for loyal BestChange fans who have actively interacted with the service.",
"image": "ipfs://QmYBrWr1MCMCxNB78RF96EzJHypZ658nUGtAQYq8VnWfqj"
}
У нас не было необходимости дополнять токены разными атрибутами, но если у вас есть такая задача, советуем ознакомиться с описанием стандарта метаданных, которые использует OpenSea: https://docs.opensea.io/docs/metadata-standards
Пример более изощрённых метаданных:
{
"name": "Example #1",
"description": "Name: BestChange Creator",
"image": "ipfs://Qmc3qZmHiYw6qiViDPhSX1wxjaeGwVuLg6TUde3zxdAbLS",
"attributes": [
{
"trait_type": "Position",
"value": "Senior analyst"
},
{
"trait_type": "Level",
"value": 5,
"max_value": 10
}
]
}
Если ваша коллекция содержит генеративные изображения, следует предварительно их сгенерировать, параллельно записывая информацию о используемых слоях в БД. После чего залить изображения в IPFS, скачать список хэшей, дополнить ими строки в БД и только после этого генерировать файлы с метаданными.
После того, как все json-файлы сгенерированы, также заливаем их в IPFS и собираем список хэшей для будущего минтинга коллекции.
Смарт-контракт для NFT-коллекции
Переходим к самому "страшному" и одновременно простейшему шагу — созданию и публикации смарт-контракта.
На самом деле, добрые разработчики из OpenZeppelin позаботились о нас и сделали простейший генератор смарт-контрактов: https://docs.openzeppelin.com/contracts/4.x/wizard
Здесь всё просто. Для нашей задачи необходимо открыть вкладку ERC721, заполняем Name и Symbol (видны потом только в обозревателях) и проставить галочки:
Mintable — чтобы токены минтил владелец контракта;
Auto Increment Ids — добавляет автоматический счётчик токенов;
Enumerable — добавляет нужные функции для обозревателей;
URI Storage — добавляет возможность установки URL с метаданными для каждого отдельного токена.
Поле Base URI оставляем нетронутым, переключатель access control по умолчанию, галочку upgradeability ставить нет необходимости (она создаёт прокси контракт, чтобы в будущем можно было заменить содержимое основного, это не по канонам децентрализации, мы делать не будем).
Осталось заполнить Security Contact (добавится в код контракта как комментарий), а вид лицензии оставляем MIT.
После этого жмём кнопку Open in Remix, откроется онлайн версия IDE уже с кодом нашего контракта.
Далее переходите на третью сверху вкладку и нажмите кнопку Compile contract-...
Результат компиляции (появится контракт и зелёная галочка в третьем разделе):
Далее нужно авторизоваться в MetaMask:
Теперь на четвёртой сверху вкладке можно подключиться к сети, которая установлена в MetaMask (Injected Provider - MetaMask). Обязательно проверяйте, чтобы в MetaMask была включена сеть Polygon Mainnet.
Если всё было установлено верно, то под строкой с сетью будет указан ID цепочки 137 (для основной сети Polygon).
После нажатия на кнопку Deploy, MetaMask попросит подписать транзакцию, после чего он отправит её в сеть, где после подтверждения контракт получит свой адрес, он понадобится нам в будущем.
! Существует какой-то необъяснимый баг. Ваша транзакция может пропасть в небытие (вероятно, MetaMask время от времени отправляет транзакции в какую-то резервную ноду, либо Remix передаёт некорректную цену за газ, но данные попадут в сеть через несколько часов).
Если вам повезло, транзакция тут же получит подтверждение, если не повезло — ждите, не верьте MetaMask, что транзакция отменилась, она не пропала бесследно, а подтвердится через несколько часов (если за это время вы наплодите ещё несколько попыток, они также "отменятся" в MetaMask, а потом одной большой пачкой подтвердятся в тот момент, когда вы этого уже не будете ждать).
Понять происхождение этого бага не удалось, поэтому не можем вам советовать что-то, чтобы его избежать.
Теперь, когда наш контракт размещён в блокчейне, следует соблюдать этикет и провести его верификацию. Для этого переходим на https://polygonscan.com, здесь необходимо создать аккаунт, после чего указать в обозревателе адрес только что созданного контракта. Найти адрес контракта можно в списке транзакций MetaMask, по ссылке "Смотреть в проводнике блоков"
На открытой странице будут видны данные транзакции, нас интересует строчка "To:", далее перейдем по ссылке в этой строке.
Без верификации обозреватель видит только байткод. Исправим это, нажав на ссылку "Verify and Publish".
Далее заполняем все поля в соответствии с настройками из Remix и жмём "Continue":
Теперь нам нужно вернуться в Remix и на нашем контракте в контекстном меню выбрать пункт "Flatten". Он сгенерирует нам единый документ со всеми использованными библиотеками.
Обязательно добавляем первой строкой: // SPDX-License-Identifier: MIT
Вставляем полученный текст в окно верификации. Проходим рекапчу и жмём "Verify and Publish"
Если всё прошло успешно, мы увидим что-то подобное:
Кстати, созданный здесь же ABI пригодится нам в будущем, поэтому советуем скопировать его и сохранить в отдельных документ.
После выполнения всех процедур контракт получил своё имя и галочку о подтверждении кода.
Минтинг NFT коллекции
Теперь переходим к самой интересной части — минтим коллекцию с помощью скрипта на Python.
Для работы скрипта нам понадобится следующее:
Адрес кошелька
Приватный ключ
Адрес контракта
ABI-контракта
Первые два пункта ищем в MetaMask.
Если скопировать адрес не составит труда, то для получения приватного ключа перейдите в раздел "Реквизиты счёта" в настройках кошелька, и нажмите кнопку "Экспорт закрытого ключа".
После чего у вас потребуют ввести пароль от кошелька и выдадут приватный ключ (храните его очень бережно, с ним любой человек получит полный доступ к вашему кошельку).
Адрес контракта можно взять в обозревателе, а ABI мы ранее копировали в отдельный файл, когда верифицировали контракт.
Далее берём этот готовый скрипт и вставляем в него необходимые данные:
import csv
import requests
from web3 import Web3
# Определение URL ноды
node_url = "https://polygon-rpc.com"
# Связь с нодой
web3 = Web3(Web3.HTTPProvider(node_url))
# Проверка соединения с нодой
if web3.isConnected():
print("-" * 50)
print("Connection Successful")
print("-" * 50)
else:
print("Connection Failed")
# Определение адреса отправителя (должен быть владелец контракта) и приватного ключа
caller = "0x24......"
private_key = "2f6......."
# Определение ABI и адреса контракта
abi = '[ { "inputs": [], .........'
contract_address = "0xc73........"
# Создание объекта с данными контракта
contract = web3.eth.contract(address=contract_address, abi=abi)
# Определение ID цепочки
Chain_id = web3.eth.chain_id
# Определение номера будущей транзакции для этого кошелька
nonce = web3.eth.getTransactionCount(caller)
# Читаем построчно данные из документа list.csv
with open('list.csv') as csvfile:
# Разделитель полей в скрипте запятая, иногда excel может устанавливать разделителем точку с запятой, проверить можно открыв csv-файл в блокноте
readCSV = csv.reader(csvfile, delimiter=',')
for row in readCSV:
# Получаем текущие цены за газ для сети Polygon
gaspricearr = requests.get('https://gasstation.polygon.technology/v2').json()
gPrice = int(float(gaspricearr['fast']['maxFee'])*1000000000)
# Проверяем формат кошелька, если он без контрольной суммы, меняем на правильный
if web3.is_checksum_address(row[0]):
toAddress = row[0]
else:
toAddress = web3.to_checksum_address(row[0])
# Определяем параметры транзакции
call_function = contract.functions.safeMint(toAddress, row[1]).buildTransaction({"chainId": Chain_id, "from": caller, "gas": 1000000, "gasPrice": gPrice, "nonce": nonce})
# Увеличиваем номер будущей транзакции на единицу
nonce += 1
# Подписываем транзакцию приватным ключём
signed_tx = web3.eth.account.sign_transaction(call_function, private_key=private_key)
# Отправляем транзакцию в блокчейн
send_tx = web3.eth.send_raw_transaction(signed_tx.rawTransaction)
# Дожидаемся обработки транзакции
tx_receipt = web3.eth.wait_for_transaction_receipt(send_tx)
# Печатаем в терминал данные опубликованной транзакции
print(tx_receipt)
Кроме скрипта нам понадобится список получателей с URI их токенов в формате CSV. Содержимое файла должно выглядеть так:
0x4C3A0d41cB765c47D3933F1a05160B7BA085DfCa,ipfs://QmQPe3Kg7FQJdAbANw6gWy8c9qT6r7LKWKFKbE5E1zSAjA
0x2e8B06982c01dc8604060F3604b544AB33bfe69D,ipfs://QmajZpQWNo1pjKCkWp1UyM43oFiv7tX3deJw23LvuHW9bJ
0x636CDce269C60E20bFBa65D3C88F6EF73F8A1a28,ipfs://QmaNF3dWa19k2YJRCErYRjn9fB9EGmPLWm6bwX76VpUHib
0x32FeB01eE4387a18cf4051F2ef7C943Bee51E2c5,ipfs://QmPU2FFPojFei38LDFqQQaQXUPL27r9PKneyozkXYUTMk6
0x275D40dFD150270C7212fc626798dde0176EFaBE,ipfs://QmUfFA9vuhjTDQW29EpA5U6R6HjRRcBMNLaLDm8yXwnGVD
0xbb1338d34740Dd6c0342e46c4c51cDf38C1CceD8,ipfs://QmahVcksCjoaxNbYuk2wqyNY2CbnYxmj4Zovq5iVH9xTei
Обратите внимание на разделитель полей, его нужно будет правильно указать в скрипте.
Финал
Всё готово, запускаем скрипт и ждём пока токены улетают своим получателям.
! Иногда, раз в 100-300 транзакций, случаются неотлавливаемые баги и скрипт прерывается с ошибкой (каждый раз новой, определить закономерность не удалось). Для продолжения следует подождать минутку, зайти в обозреватель и найти в истории транзакций своего кошелька ID последнего отправленного токена, удалить в CSV все ранее обработанные строки, сохранить и перезапустить скрипт.
Коллекция заминтилась и сразу разлетелась получателям, мы достигли успеха. Если хотите быть единоличным владельцем, проставьте свой же кошелёк в поле получателя в CSV-файле, не оставляйте пустым.
Бонус
Если для хранения метаданных вы используете IPFS, то одна из популярных площадок — Rarible (используется VK для импорта данных) имеет свойство время от времени не загружать метаданные из этой файловой системы, если шлюз не отдал их за необходимое время.
Для проверки корректности рекомендуем использовать их же API:
<?php
$body = file_get_contents('https://api.rarible.org/v0.1/items/byCollection?collection=POLYGON:0x...тут_адрес_контракта...&size=556'); //Получаем в $body json строку с нужным количеством записей
$arr = json_decode($body, true); //Разбираем json запрос на массив в переменную $arr
foreach ($arr['items'] as $nums){
echo $nums['meta']['name'].'<br>'; //Построчно выводим имена токенов, если какого-то не хватает, значит Rarible не скачал данные
}
?>
Если нет нескольких строк, пишите им в поддержку через форму обратной связи. Через несколько дней они поправят это.