Create Metadata

This assumes there already was a previous deployment to rinkeby, with the utilization of AdvancedCollectible[-1]

Setup

  • create new folder "metadata" in brownie project root

  • create new file: sample_metadata.py

  • create new folder within metadata, "rinkeby"

sample_metadata.py
metadata_template = {
    "name": "",
    "description": "",
    "image": "",
    "attributes": [{"trait_type": "cuteness", "value": 100}]
}

create_metadata.py

In our scripts folder, create new file: create_metadata.py

We will use this script to assign the URI for each NFT.

talk about NFT token URI etc - what, how

create_metadata.py
from brownie import AdvancedCollectible, network
from metadata.sample_metadata import metadata_template
from pathlib import Path

breed_mapping = {0: "PUG", 1: "SHIBA_INU", 2: "ST_BERNARD"}

def get_breed(breed_number):
    return breed_mapping[breed_number]


def main():
    advanced_collectible = AdvancedCollectible[-1]
    number_of_NFTS = advanced_collectible.tokenCounter()
    print(f"The number of NFTs minted so far is {number_of_NFTS}")

    for tokenID in range(number_of_NFTS):
        breed = get_breed(advanced_collectible.tokenIdToBreed(tokenID))
        metadata_filename = f"./metadata/{network.show_active()}/{tokenID}-{breed}.json"
    
        collectible_metadata = metadata_template
        if Path(metadata_filename).exists():
            print(f"{metadata_filename} exists! Delete to overwrite")
        else:
            print(f"Creating Metadata file: {metadata_filename}")
            collectible_metadata["name"] = breed
            collectible_metadata["description"] = f"An adorable {breed} pup!"
            print(collectible_metadata)

            image_path = "./img" + breed.lower().replace("_", "-") + ".png"
            image_uri = upload_to_ipfs()
            collectible_metadata["image_uri"] = image_uri
  • call on previous rinkeby deployment

  • check number of NFTs minted

  • for each tokenID, get the breed via getbreed(), and structure the filename and path as metadata_filename

  • collectible_metadata collects the metadata_template to be used as a base for modification

  • We then check if the required metadata_filename exists, using Path library.

  • Else, we will create a new one using the template as reference, adding the following

    • name

    • description

    • image URI -> collectible_metadata["image_uri"] = image_uri

image-uri

How do we get the image URI? Currently the images are in our local img folder. We need them to be hosted on IPFS and pass their IPFS URI into collectible_metadata["image_uri"]

To that end, we will have to install IPFS cli: https://docs.ipfs.io/how-to/command-line-quick-start/

Interestingly, ipfs daemon command works in terminal, command prompt.

On running ipfs daemon on terminal we will see the following:

We will be working with /api/v0/add to add our file to IPFS: https://docs.ipfs.io/reference/http/api/#http-rpc-commands

def upload_to_ipfs(filepath):
    # open image as binary - open(rb)
    with Path(filepath).open("rb") as fp:
        image_binary = fp.read()
        ipfs_url = "http://127.0.0.1:5001"     #get from WebUI 
        endpoint = "/api/v0/add"
        response = requests.post(ipfs_url + endpoint, files={"file":image_binary}) #post request
        ipfs_hash = response.json()["Hash"]     #response returns dictionary
        # "./img/0-PUG.png"  -> split on /, grab last part of array, which is "0-PUG.png"
        filename = filepath.split("/")[-1:][0]
        image_uri = f"https://ipfs.io/ipfs/{ipfs_hash}?filename={filename}"
        print(image_uri)
        return image_uri
  • ipfs_url -> retrieved from running ipds daemon

  • endpoint -> as per api endpoint reference

  • response -> structuring and sending a post response (can also use curl)

  • ipfs_hash -> response returns JSON string on successful call to endpoint - decode it as a python dict via .json()

  • extract filename from filepath and together with the IPFS hash, construct the image URI

will return an ipfs link, like so:

https://ipfs.io/ipfs/QmUPjADFGEKmfohdTaNcWhp7VGk26h5jXDA7v3VtTnTLcW?filename=st-bernard.png

The image will be available as long as you are running your ipfs node.

To make the data highly available without needing to run a local IPFS daemon 24/7, you can request that a remote pinning service store a copy of your IPFS data on their IPFS nodes.

Creating metadate files

Now that we have filled all the fields in the metadata file, we are going to write it as a json file into our directory.

        with open(metadata_filename, "w") as file:
            json.dump(collectible_metadata, file)
        upload_to_ipfs(metadata_filename)

This will create a json file in our metadata/rinkeby folder upon running:

brownie run scripts/advcollectible/create_metadata.py --network rinkeby

Final code at this point:

from brownie import AdvancedCollectible, network
from metadata.sample_metadata import metadata_template
from pathlib import Path
import requests, json

breed_mapping = {0: "PUG", 1: "SHIBA_INU", 2: "ST_BERNARD"}

def get_breed(breed_number):
    return breed_mapping[breed_number]


def main():
    advanced_collectible = AdvancedCollectible[-1]
    number_of_NFTS = advanced_collectible.tokenCounter()
    print(f"The number of NFTs minted so far is {number_of_NFTS}")

    for tokenID in range(number_of_NFTS):
        breed = get_breed(advanced_collectible.tokenIdToBreed(tokenID))
        metadata_filename = f"./metadata/{network.show_active()}/{tokenID}-{breed}.json"
    
        collectible_metadata = metadata_template
        if Path(metadata_filename).exists():
            print(f"{metadata_filename} exists! Delete to overwrite")
        else:
            print(f"Creating Metadata file: {metadata_filename}")
            collectible_metadata["name"] = breed
            collectible_metadata["description"] = f"An adorable {breed} pup!"
            print(collectible_metadata)
            # convert underscores to dashes to be URI compatible
            image_path = "./img/" + breed.lower().replace("_", "-") + ".png"
            image_uri = upload_to_ipfs(image_path)
            collectible_metadata["image_uri"] = image_uri
            
            with open(metadata_filename, "w") as file:
                json.dump(collectible_metadata, file)
            upload_to_ipfs(metadata_filename)


def upload_to_ipfs(filepath):
    # open image as binary - opne(rb)
    with Path(filepath).open("rb") as fp:
        image_binary = fp.read()
        ipfs_url = "http://127.0.0.1:5001"     #get from WebUI 
        endpoint = "/api/v0/add"
        response = requests.post(ipfs_url + endpoint, files={"file": image_binary})       #post request
        ipfs_hash = response.json()["Hash"]     #response returns dictionary. 
        # "./img/0-PUG.png"  -> split on /, grab last part of array, which is "0-PUG.png"
        filename = filepath.split("/")[-1:][0]
        image_uri = f"https://ipfs.io/ipfs/{ipfs_hash}?filename={filename}"
        print(image_uri)
        return image_uri   

11:33 - Refactor to check if file has already been uploaded to IPFS, so we don't upload every single time. (check github for code)

Alternative: Pinata

deploy to Pinata -> upload_pinata.py

Reference: https://docs.pinata.cloud/api-pinning/pin-file

  • create account at Pinata and generate api key

  • 11:20 - 11:31

You should also see the pinned file:

Last updated