Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Привет Хабр.
История началась пол года назад, когда ко мне пришел мой старый знакомый художник и предложил создать NFT коллекцию на блокчейне Ethereum. Скажу сразу, я никогда не интересовался криптой и на тот момент понятие не имел, что такое NFT и как они устроены.
В этой статья я не буду рассказывать, что такое NFT и для чего они нужны, вместо этого я сосредоточусь на технической части, т.к. когда я начинал материала было крайне мало и приходилось додумывать некоторые решения самому.
После проведенного небольшого исследования, мне удалось разбить изначальную задачу из абстрактного “создать NFT коллекцию” на более мелкие и конкретные, а именно:
с генерировать 10 000 уникальных изображений
с генерировать 10 000 метаданных к каждому изображению
загрузить 10 000 изображений вместе с метеданными в децентрализованное хранилище
создать смарт-контракт для NFT токенов
загрузить созданный смарт-контракт в mainnet Ethereum
создать сайт, который будет взаимодействовать с нашим смарт-контрактом с помощью web3, где собственно пользователи и смогут менять свои эфиры на наши NFT токены
Казалось бы пустяки, а нет, на каждом этапе меня поджидали непредвиденные моменты, о которых мы сейчас и поговорим.
Как с генерировать 10 000 уникальных изображений?
Почему именно 10 000? Ответ достаточно прост, большинство популярных NFT проектов, предлагают коллекции именно из 10 000 NFT токенов. Каждый создатель, сам волен решать сколько NFT токенов он хочет выпустить, но мы решили не отходить от канона и тоже сделали 10 000 токенов.
Итак, как же все таки с генерировать 10 000 уникальных изображений? Конечно же с помощью автоматического наложения слоев друг на друга. Немного поразмыслив, мы с художником пришли к выводу, что для нашего проекта нам нужны следующие слои:
фон - 20 шт
туловище персонажа - 25 шт
голова - 15 шт
эмоции - 20 шт
одежда - 30 шт
обувь - 25 шт
аксессуары - 40 шт
В общем количестве, у нас получилось приблизительно 175 уникальных слоев в формате png, что более чем достаточно, чтобы получить 10 000 уникальных персонажей. Теперь осталось совсем ничего, а именно написать утилиту, которая на входе будет принимать заготовки в виде слоев, а на выходе будет отдавать готовых персонажей.
Писать я буду на Golang, итак поехали. Для начала, нам нужно описать 2 структуры в пакете domain, одна для слоев, а другая для холста.
package domain
import (
"image"
"image/color"
)
// ImageLayer struct.
type ImageLayer struct {
Image image.Image
Priotiry int
XPos int
YPos int
}
//BgProperty is background property struct.
type BgProperty struct {
Width int
Length int
BgColor color.Color
}
Давайте более подробно рассмотрим обе структуры.
ImageLayer:
Image - изображение слоя
Priority - приоритет слоя, т.к. слои нужно накладывать в определенном порядке, сначала фон, потом туловище, потом голова, итд …
XPos, YPos - позиция слоя на холсте
BgProperty:
Width - ширина холста
Length - длина холста
Итак, когда базовые структуры описаны, мы можем перейти к написанию сервиса, который собственно и будет комбинировать наши слои в определенном порядке.
Код сервиса достаточно простой, на входе сервис принимает список слоев и параметры холста, а на выходе возвращает байты сгенерированного изображения. Хочется отметить, что Go обладает достаточно хорошей библиотекой для работы с изображениями и именно с ней мы будем работать, собственно код:
package combiner
import (
"bytes"
"image"
"image/draw"
"image/png"
"nft/internal/domain"
"sort"
)
type service struct {
}
func NewBasicImageCombiner() domain.ImageCombiner {
return &service{}
}
func (s *service) CombineLayers(layers []*domain.ImageLayer, bgProperty *domain.BgProperty) ([]byte, error) {
// Sort list by position.
layers = sortByPriotiry(layers)
// Create image's background.
bgImg := image.NewRGBA(image.Rect(0, 0, bgProperty.Width, bgProperty.Length))
// Set the background color.
draw.Draw(bgImg, bgImg.Bounds(), &image.Uniform{bgProperty.BgColor}, image.Point{}, draw.Src)
// Looping image layers, higher position -> upper layer.
for _, img := range layers {
// Set the image offset.
offset := image.Pt(img.XPos, img.YPos)
// Combine the image.
draw.Draw(bgImg, img.Image.Bounds().Add(offset), img.Image, image.Point{}, draw.Over)
}
// Encode image to buffer.
buff := new(bytes.Buffer)
if err := png.Encode(buff, bgImg); err != nil {
return nil, err
}
return buff.Bytes(), nil
}
func sortByPriotiry(list []*domain.ImageLayer) []*domain.ImageLayer {
sort.Slice(list, func(i, j int) bool {
return list[i].Priotiry < list[j].Priotiry
})
return list
}
Отлично, когда код для генерации изображений готов, мы можем переходить к генерации метаданных.
Для начала, стоит отметить, что именно в метаданных хранятся все свойства и ссылки на картинки для NFT токенов, а также именно по метаданным, большинство торговых площадок выполняют поиск NFT токенов. Поэтому очень важно выбрать правильный формат для метаданных.
В каком формате должны быть метаданные для NFT токенов?
Т.к. NFT токены основаны на ERC-721 стандарте, а сам стандарт никак не описывает в каком формате должны быть метаданные, мы вольны использовать любой формат какой только захотим.
Но если мы хотим, чтобы наши NFT токены могли полноценно торговаться на таких пощадках как opensea, мы должны следовать следующему JSON формату:
{
"image":"ipfs://QmPbxeGcXhYQQNgsC6a36dDyYUcHgMLnGKnF8pVFmGsvqi",
"attributes":[
{
"trait_type":"Mouth",
"value":"Grin"
},
{
"trait_type":"Clothes",
"value":"Vietnam Jacket"
},
{
"trait_type":"Background",
"value":"Orange"
},
{
"trait_type":"Eyes",
"value":"Blue Beams"
},
{
"trait_type":"Fur",
"value":"Robot"
}
]
}
Отлично, когда мы разобрались с форматом, давайте опишем структуру для хранения метаданных на Go:
package domain
// ERC721Trait - ERC721 trait format.
type ERC721Trait struct {
TraitType string `json:"trait_type"`
Value string `json:"value"`
}
// ERC721Metadata - metadata schema.
type ERC721Metadata struct {
Image string `json:"image"`
Attributes []*ERC721Trait `json:"attributes"`
}
Перед тем как приступать к генерации метаданных, нам нужны готовые ссылки на загруженные в децентрализованное хранилище изображения. В следующей статье, я расскажу как загружать изображения в децентрализованное хранилище, а также продолжим работать с метаданными.