Step-by-step instructions on how to create an NFT collection on Golang + Ethereum

In this article, I will not talk about what NFT is and what they are for, instead, I will focus on the technical part, because when I started, there was very little material and I had to think out some solutions myself.

After doing a little research, I was able to break the original task from an abstract “create an NFT collection” into smaller and more concrete ones, namely:

  • s generate 10,000 unique images

  • with generate 10,000 metadata for each image

  • upload 10,000 images along with metadata to a decentralized storage

  • create a smart contract for NFT tokens

  • upload the created smart contract to the mainnet Ethereum

  • create a website that will interact with our smart contract using web3, where the users themselves will be able to exchange their ethers for our NFT tokens

It would seem trifles, but no, at each stage unexpected moments awaited me, which we will now talk about.

How to generate 10,000 unique images?

Why exactly 10,000? The answer is quite simple, most of the popular NFT projects offer collections of exactly 10,000 NFT tokens. Each creator is free to decide how many NFT tokens he wants to issue, but we decided not to deviate from the canon and also made 10,000 tokens.

So how do you generate 10,000 unique images? Of course, with the help of automatic blending of layers. After a little reflection, the artist and I came to the conclusion that for our project we need the following layers:

  1. background – 20 pieces

  2. character’s torso – 25 pieces

  3. head – 15 pieces

  4. emotions – 20 pcs

  5. clothes – 30 pieces

  6. shoes – 25 pieces

  7. accessories – 40 pcs

In total, we ended up with approximately 175 unique png layers, which is more than enough to get 10,000 unique characters. Now there is absolutely nothing left, namely, to write a utility that at the input will accept blanks in the form of layers, and at the output will give ready-made characters.

I’ll write on Golang, so let’s go. First, we need to define 2 structures in the domain package, one for the layers and one for the canvas.

package domain

import (
	"https://habr.com/ru/post/595723/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
}

Let’s take a closer look at both structures.

ImageLayer:

  • Image – layer image

  • Priority – the priority of the layer, because layers need to be applied in a certain order, first the background, then the body, then the head, etc.

  • XPos, YPos – position of the layer on the canvas

BgProperty:

So, when the basic structures are described, we can move on to writing a service that, in fact, will combine our layers in a specific order.

The service code is quite simple, at the input the service accepts a list of layers and canvas parameters, and at the output it returns bytes of the generated image. I would like to note that Go has a fairly good library for working with images and it is with it that we will work, the actual code:

package combiner
 
import (
   "bytes"
   "https://habr.com/ru/post/595723/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
}

Great, when the code for generating images is ready, we can move on to generating metadata.

To begin with, it is worth noting that it is in the metadata that all properties and links to pictures for NFT tokens are stored, and also precisely by metadata, most trading platforms search for NFT tokens. Therefore, it is very important to choose the correct format for the metadata.

What format should the metadata for NFT tokens be in?

Because NFT tokens are based on the ERC-721 standard, and the standard itself does not describe in any way what format the metadata should be in, we are free to use any format we want.

But if we want our NFT tokens to be fully traded on platforms such as opensea, we must follow the following JSON format:

{
   "https://habr.com/ru/post/595723/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"
      }
   ]
}

Great, now that we’ve figured out the format, let’s describe the structure for storing metadata in 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:"https://habr.com/ru/post/595723/image"`
	Attributes []*ERC721Trait `json:"attributes"`
}

Before starting to generate metadata, we need ready-made links to images uploaded to the decentralized storage. In the next article, I will explain how to upload images to decentralized storage, and also continue to work with metadata.

Similar Posts

Leave a Reply