First time I'm writing a smart contract. If anything doesn't make sense at all, feel free to call out what specifically doesn't make sense and suggest a better approach. Thanks!

At a high level I just want to store and fetch important immutable metadata on the NFT. In my example this would be x/y coordinates and width/height of it.

class TezosLandNft(FA2.FA2):
    def mint(self, params):
        sp.verify(~ self.token_id_set.contains(self.data.all_tokens, params.token_id), "NFT-asset: cannon mint the same token twice")
        self.token_id_set.add(self.data.all_tokens, params.token_id)
        # TODO: add back ledger bookkeeping code to maintain FA2 compliance
        self.data.tokens[params.token_id] = self.token_meta_data.make(
                amount = 1, 
                metadata = TezosLandNft.make_metadata(
                            decimals = 0,
                            name = "TezosLand",
                            symbol = "TZL ",
                            x = params.x,
                            y = params.y,
                            width = params.width,
                            height = params.height

    def make_metadata(symbol, name, decimals, x, y, width, height):
        "Helper function to build metadata JSON bytes values."
        return (sp.map(l = {
            # Remember that michelson wants map already in ordered
            "decimals" : sp.bytes_of_string("%d" % decimals),
            "name" : sp.bytes_of_string(name),
            "symbol" : sp.bytes_of_string(symbol),
            "attributes": sp.map({
                "x": sp.bytes_of_string(str(x)),
                "y": sp.bytes_of_string(str(y)),
                "width": sp.bytes_of_string(str(width)),
                "height": sp.bytes_of_string(str(height)) 

Having the custom attributes under the "attributes" key throws an error (as a temporary workaround I have been putting the custom keys at the top level):

Error: Type Error sp.TMap(sp.TString, sp.TBytes) is not sp.TBytes bad type for map value

Then the issue I'm having is figuring out how to fetch the NFT's meta_data for all existing token ids... Should this be a new entrypoint on the TezosLandNft FA2 contract?

Also, could the code below be simplified such that all the state is stored within the NFT and is that advisable? There are certain metadata fields that should be immutable (x,y,width,height), but others are mutable. Would overriding the FA2 contract set_metdata entry point work for this? Or is there a better approach to this?

class TezosWorld(sp.Contract):
    def __init__(self, admin, land):
        grid = {}
        self.land_nft = land
        self.init(admin=admin, grid=grid, lands={})

    def buy(self, params):
        land_id = sp.len(self.data.lands)
        self.land_nft.mint(token_id=land_id, address=sp.sender, amount=1, x=params.x, y=params.y, height=params.height, width=params.width)
        self.data.lands[land_id] = sp.record(title="", image="", link="",nsfw=False, land_id=land_id)
        # how to fetch metadata by token_id later?

1 Answer 1


Your code above has some issues that I will describe below. Also, you are confusing off-chain metadata with on-chain metadata, tzip16 and tzip21 allow those structures because they were designed to be stored off-chain. (Only possible to store onchain with stringified JSON or in bytes.)

In make_metadata you are trying to create a map with different value types. This is not possible since Michelson is statically typed and you must follow the type definition.


With the following type sp.TMap(sp.TString, sp.TBytes) you can only have keys as strings and values as bytes. In your code above, the attributes field doesn't comply with this.


In the second contract you have self.land_nft.mint which is invalid, you must use sp.transfer for this.


class TezosWorld(sp.Contract):
def __init__(self, admin, land_nft_address):
    grid = {}
    self.init(admin=admin, grid=grid, lands={}, land_nft_address = land_nft_address)

def buy(self, params):
    land_id = sp.len(self.data.lands)
    land_nft_contract = sp.contract(
      entry_point = "mint"
    ).open_some(message = "Invalid Interface")
    params = sp.record(token_id=land_id, address=sp.sender, amount=1, x=params.x, y=params.y, height=params.height, width=params.width)
    sp.transfer(params, sp.tez(0), land_nft_contract)

I recommend you to check the tzcolors contract, I think it does exactly what you are trying to do.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.